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;

        }
    }

}

person haferblues    schedule 05.09.2011    source источник
comment
не относящийся к рассматриваемой проблеме: а) никогда не создавайте компоненты в getXXRendererComponent б) не расширяйте классы, если новые не удовлетворяют требованиям is-a   -  person kleopatra    schedule 05.09.2011
comment
Обратите внимание, что SSCCE должен включать импорт. Не все из нас используют автоматические IDE, которые могут их понять!   -  person Andrew Thompson    schedule 05.09.2011


Ответы (2)


Редактировать 1: к сожалению, увидев снимок экрана @Andrew, понял, что это не работает должным образом, текст на самом деле длиннее, чем показано с этим (пропустил внутренний комментарий «ОЖИДАНИЕ: не работает для JList в JScrollPane " ;-) Немного покопаюсь и удалю этот ответ, если не смогу заставить его работать в ближайшее время.

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

  • BasicListUI не принимает во внимание, что изменение размера списка может потребовать очистки кэша внутреннего размера (фактически высоты строки), код приложения должен принудительно сделать это, т.е. в 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's internal sizing mechanism into action
        if (width > 0)
            ta.setSize(width, Short.MAX_VALUE);
        return p;

    }
}
person kleopatra    schedule 05.09.2011
comment
это на самом деле работает идеально. Я просто заменил int width=list.getWidth() на int width = list.getWidth() - l.getIcon().getIconWidth(); В противном случае высота текстовой области рассчитывается так, как если бы для нее была доступна общая ширина списка. Большое спасибо. Я тоже многому сегодня научился. - person haferblues; 06.09.2011
comment
@haferblues - хороший улов подстраивается под остальную ширину, спасибо :-) - person kleopatra; 06.09.2011
comment
@kleopatra - хорошее решение! Я рвал на себе волосы! - person Steve Cohen; 03.07.2013
comment
@kleopatra - Я снова рву на себе волосы. Иногда это обрезает последнюю строку текста. Я не понимаю, почему. А в другое время - нет. - person Steve Cohen; 26.10.2013

Многострочный список

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;
        }
    }
}
person Andrew Thompson    schedule 05.09.2011
comment
блин.. опять избили, с скриншотом :-) - person kleopatra; 05.09.2011
comment
Вы были слишком заняты, давая хорошие советы о том, как не создавать компоненты в рендерере. ;) - person Andrew Thompson; 05.09.2011
comment
stackoverflow.com /questions/6901153/ , но оба +1 - person mKorbel; 05.09.2011
comment
Изменение размера окна не приводит к увеличению текста. Это решение требует дополнительной работы. - person Daniele Testa; 14.03.2014