15

I already read/tried these posts but that didn't help:

What I need is a ListCellRenderer which returns a panel with an icon on the left and a text of dynamic length on the right (like in any forum: on the left a user avatar, on the right the post text). The texts are NOT known to me, so I can't set a fixed cell height. Further, the text length differs from list cell to list cell. So every list cell needs its own height depending on the length of the text. Actually a really common layout ... but not for Swing. The cell height just doesn't expand according to the text length.

I already read almost any post out there about dynamic cell heights and multiline texts in JList, but couldn't find a solution. So I decided to give a small SSCCE. Please give me a hint on how to achieve what I described or please fix my code if you think it's easy.

Thanks

Here is ths 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;

        }
    }

}
Community
  • 1
  • 1
haferblues
  • 2,155
  • 4
  • 26
  • 39
  • unrelated to the problem at hand: a) never-ever _create_ components in the getXXRendererComponent b) dont _extend_ classes if the new doesn't fulfill the is-a requirement – kleopatra Sep 05 '11 at 09:49
  • 1
    Note that an SSCCE should include the imports. Not all of us use automagic IDEs that can figure them out! – Andrew Thompson Sep 05 '11 at 10:14

2 Answers2

18

Edit 1: oops - seeing @Andrew's screenshot, realized that this isn't working as expected, the text is actually longer than shown with this (overlooked an internal comment "PENDING: not working for JList in JScrollPane" ;-) Will dig a bit and delete this answer if I can't make it work soon.

Edit 2: got it - the renderer implementation as shown below is okay, the culprit is the JList with its occasional less than optimal size caching. There are two parts of that

  • BasicListUI doesn't take into account that resizing the list might require clearing the internal size (actually row height) cache, application code must force it to do so, f.i. in a ComponentListener
  • list's Scrollable implementation of tracksViewportWidth contains logic which stands in the way (leads to looping stretch-out of the area until it's a single line), subclass to return true.

Code that uses the renderer below:

    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));

First answer (a renderer implementation which uses JTextArea as rendering component)

TextArea is a bit tricky in sizing: it needs to get initialized to something reasonable:

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;

    }
}
kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • this actually works perfect. I just replaced int width=list.getWidth() by int width = list.getWidth() - l.getIcon().getIconWidth(); Otherwise the height of the textarea is calculated as if the total width of the list is available for it. Thank you so much. I also learned a lot today. – haferblues Sep 06 '11 at 12:10
  • @haferblues - good catch adjusting to the rest width, thanks :-) – kleopatra Sep 06 '11 at 12:42
  • @kleopatra - nice fix! I was tearing my hair out! – Steve Cohen Jul 03 '13 at 18:51
  • @kleopatra - I am tearing my hair out again. Sometimes, this cuts off the last line of text. I don't understand why. And then at other times, it doesn't. – Steve Cohen Oct 25 '13 at 20:55
14

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;
        }
    }
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433