Подтвердить что ты не робот

Swing JList с многострочным текстом и динамической высотой

Я уже читал/пробовал эти сообщения, но это не помогло:

Мне нужен ListCellRenderer, который возвращает панель с иконкой слева и текстом динамической длины справа (например, на любом форуме: слева - аватар пользователя, справа - текст сообщения), Тексты НЕ известны мне, поэтому я не могу установить фиксированную высоту ячейки. Кроме того, длина текста отличается от ячейки списка до ячейки списка. Поэтому каждая ячейка списка нуждается в собственной высоте в зависимости от длины текста. На самом деле действительно общий макет... но не для Swing. Высота ячейки просто не расширяется в соответствии с длиной текста.

Я уже прочитал почти любую публикацию о динамических высотах ячеек и многострочных текстах в JList, но не смог найти решение. Поэтому я решил дать небольшой SSCCE. Пожалуйста, дайте мне подсказку о том, как достичь того, что я описал, или, пожалуйста, исправить мой код, если вы считаете это легким.

Спасибо

Здесь находится SSCCE:

public class MultiLineList extends JFrame
{

    private static final long serialVersionUID = 1L;

    public static void main(final String[] args)
    {
        new MultiLineList();
    }

    private MultiLineList()
    {
        setTitle("MultiLineList");
        setSize(800, 450);
        setResizable(true);
        setVisible(true);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        this.getContentPane().setLayout(new BorderLayout());

        final DefaultListModel model = new DefaultListModel();
        model.addElement("This is a short text");
        model.addElement("This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. ");
        model.addElement("This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. ");

        final JList list = new JList(model);
        list.setCellRenderer(new MyCellRenderer());

        this.add(list);

        this.getContentPane().invalidate();
        this.getContentPane().validate();

    }

    public class MyCellRenderer extends DefaultListCellRenderer
    {
        @Override
        public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean hasFocus)
        {

            final String text = (String) value;

            //create panel
            final JPanel p = new JPanel();
            p.setLayout(new BorderLayout());

            //icon
            final JPanel IconPanel = new JPanel(new BorderLayout());
            final JLabel l = new JLabel("icon"); //<-- this will be an icon instead of a text
            IconPanel.add(l, BorderLayout.NORTH);
            p.add(IconPanel, BorderLayout.WEST);

            //text
            final JTextArea ta = new JTextArea();
            ta.setText(text);
            ta.setLineWrap(true);
            ta.setWrapStyleWord(true);
            p.add(ta, BorderLayout.CENTER);

            return p;

        }
    }

}
4b9b3361

Ответ 1

Изменить 1: oops - увидев скриншот @Andrew, понял, что это работает не так, как ожидалось, текст на самом деле длиннее, чем показано с этим (пропущено внутреннее замечание "PENDING: не работает для JList в JScrollPane";-) Копайте немного и удалите этот ответ, если я не могу заставить его работать в ближайшее время.

Изменить 2: получилось - реализация рендеринга, как показано ниже, в порядке, виновником является JList с его случайным кэшем менее оптимального размера. Есть две части этого

  • BasicListUI не учитывает, что для изменения размера списка может потребоваться очистка кеша внутреннего размера (фактически высота строки), код приложения должен заставить его сделать это, f.i. в ComponentListener
  • Список Прокручиваемая реализация trackViewportWidth содержит логику, которая стоит на пути (приводит к растягиванию цикла из области до одной строки), подкласс возвращает true.

Код, который использует средство визуализации ниже:

    final JList list = new JList(model) {

        /** 
         * @inherited <p>
         */
        @Override
        public boolean getScrollableTracksViewportWidth() {
            return true;
        }


    };
    list.setCellRenderer(new MyCellRenderer());

    ComponentListener l = new ComponentAdapter() {

        @Override
        public void componentResized(ComponentEvent e) {
            // next line possible if list is of type JXList
            // list.invalidateCellSizeCache();
            // for core: force cache invalidation by temporarily setting fixed height
            list.setFixedCellHeight(10);
            list.setFixedCellHeight(-1);
        }

    };

    list.addComponentListener(l);
    add(new JScrollPane(list));

Первый ответ (реализация рендерера, использующая JTextArea как компонент рендеринга)

TextArea немного сложнее в определении размеров: ему нужно инициализировать что-то разумное:

public class MyCellRenderer implements ListCellRenderer {

    private JPanel p;
    private JPanel iconPanel;
    private JLabel l;
    private JTextArea ta;

    public MyCellRenderer() {
        p = new JPanel();
        p.setLayout(new BorderLayout());

        // icon
        iconPanel = new JPanel(new BorderLayout());
        l = new JLabel("icon"); // <-- this will be an icon instead of a
                                // text
        iconPanel.add(l, BorderLayout.NORTH);
        p.add(iconPanel, BorderLayout.WEST);

        // text
        ta = new JTextArea();
        ta.setLineWrap(true);
        ta.setWrapStyleWord(true);
        p.add(ta, BorderLayout.CENTER);
    }

    @Override
    public Component getListCellRendererComponent(final JList list,
            final Object value, final int index, final boolean isSelected,
            final boolean hasFocus) {

        ta.setText((String) value);
        int width = list.getWidth();
        // this is just to lure the ta internal sizing mechanism into action
        if (width > 0)
            ta.setSize(width, Short.MAX_VALUE);
        return p;

    }
}

Ответ 2

Multi-Line List

import java.awt.*;
import javax.swing.*;

public class MultiLineList
{
    private static final long serialVersionUID = 1L;

    public static void main(final String[] args)
    {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new MultiLineList();
            }
        });
    }

    private MultiLineList()
    {
        JFrame f = new JFrame("MultiLineList");
        f.setResizable(true);
        f.setVisible(true);
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.getContentPane().setLayout(new BorderLayout());

        final DefaultListModel model = new DefaultListModel();
        model.addElement("This is a short text");
        model.addElement("This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. ");
        model.addElement("This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. This is an even longer text. ");

        final JList list = new JList(model);
        list.setCellRenderer(new MyCellRenderer());

        f.add(list);

        f.pack();
    }

    public class MyCellRenderer extends DefaultListCellRenderer
    {
        final JPanel p = new JPanel(new BorderLayout());
        final JPanel IconPanel = new JPanel(new BorderLayout());
        final JLabel l = new JLabel("icon"); //<-- this will be an icon instead of a text
        final JLabel lt = new JLabel();
        String pre = "<html><body style='width: 200px;'>";

        MyCellRenderer() {
            //icon
            IconPanel.add(l, BorderLayout.NORTH);
            p.add(IconPanel, BorderLayout.WEST);

            p.add(lt, BorderLayout.CENTER);
            //text
        }

        @Override
        public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean hasFocus)
        {
            final String text = (String) value;
            lt.setText(pre + text);

            return p;
        }
    }
}