java 带有多行文本和动态高度的 Swing JList

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7306295/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 19:29:08  来源:igfitidea点击:

Swing JList with multiline text and dynamic height

javaswingjlistexpandlistcellrenderer

提问by haferblues

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

我已经阅读/尝试过这些帖子,但这没有帮助:

What I need is a ListCellRendererwhich 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 NOTknown 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.

我需要的是一个ListCellRenderer面板,它在左侧返回一个图标,在右侧返回一个动态长度的文本(就像在任何论坛中一样:左侧是用户头像,右侧是帖子文本)。我知道这些文本,所以我无法设置固定的单元格高度。此外,文本长度因列表单元格而异。所以每个列表单元格都需要自己的高度,具体取决于文本的长度。实际上是一个非常常见的布局……但不适用于 Swing。单元格高度不会根据文本长度扩展。

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.

我已经阅读了几乎所有关于动态单元格高度和多行文本的帖子JList,但找不到解决方案。所以我决定给一个小的SSCCE。请给我一个关于如何实现我所描述的提示,或者如果您认为这很容易,请修复我的代码。

Thanks

谢谢

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

        }
    }

}

回答by kleopatra

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.

编辑 1:哎呀 - 看到@Andrew 的截图,意识到这没有按预期工作,文本实际上比显示的要长(忽略了内部评论“PENDING: not working for JList in JScrollPane”;-)如果我不能很快让它工作,请删除这个答案。

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

编辑 2:明白了 - 如下所示的渲染器实现没问题,罪魁祸首是 JList 偶尔小于最佳大小缓存。有两个部分

  • 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.
  • BasicListUI 没有考虑调整列表大小可能需要清除内部大小(实际上是行高)缓存,应用程序代码必须强制它这样做,在 ComponentListener 中
  • 列表的tracksViewportWidth 的Scrollable 实现包含阻碍的逻辑(导致循环拉伸该区域直到它是单行),子类返回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)

第一个答案(使用 JTextArea 作为渲染组件的渲染器实现)

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

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;

    }
}

回答by Andrew Thompson

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