Java Swing:JList与ListCellRenderer选择的项目高度不同

Jaa*_*aap 4 java height swing jlist

我正在制作一个自定义的ListCellRenderer.我知道每个单元格可以有不同的尺寸.但现在我想为所选单元格设置不同的维度.不知何故,JList在第一次计算每个单元格的边界时,为每个单独的单元格缓存维度.这是我的代码:

public class Test {

    static class Oh extends JPanel {

        public Oh() {
            setPreferredSize(new Dimension(100, 20));
        }

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(Color.WHITE);
            g.fillRect(0, 0, getWidth(), getHeight());
        }
    }

    static class Yeah extends JPanel {
        private boolean isSelected;

        public Yeah(boolean isSelected) {
            setPreferredSize(new Dimension(100, 100));
            this.isSelected = isSelected;
        }

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            //setSize(100, 100); // doesn't change the bounds of the component
            //setBounds(0, 0, 100, 100); // this doesn't do any good either.
            if (isSelected) g.setColor(Color.GREEN);
            else g.setColor(Color.BLACK);
            g.fillRect(0, 0, getWidth(), getHeight());
        }
    }

    public static void main(String[] args) {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.setSize(800, 500);
        Vector<Integer> ints = new Vector<Integer>();
        for (int i = 0; i < 100; i++) {
            ints.add(i);
        }
        JList list = new JList(ints);
        list.setCellRenderer(new ListCellRenderer() {
            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
                if (isSelected || ((Integer) value) == 42) return new Yeah(isSelected);
                else return new Oh();
            }
        });
        //list.setPrototypeCellValue(null);
        //list.setFixedCellHeight(-1);
        f.add(new JScrollPane(list));
        f.setVisible(true);
    }
}
Run Code Online (Sandbox Code Playgroud)

在评论中,您可以看到我已经尝试过的内容.

我已经搜索了很长时间,发现了很多无用的文章,其中一些文章触及ListCellRenderer /动态高度的东西,但它们只能起作用,因为单个细胞的高度保持不变.我的高度在变,所以我该怎么做?

kle*_*tra 8

基本上,问题有两个方面,都位于ui委托中

  • 在测量时,它无法将渲染器配置为其真实状态,这完全忽略了选择(和焦点)
  • 众所周知,它不会被迫重新计算缓存的单元格大小:它没有公开的api这样做,只能自愿进行模型更改.

解决第一个问题的方法确实是渲染器:实现忽略给定的选定标志并查询列表中的实际选择,如@Andy所述.在代码中,使用OP的组件

ListCellRenderer renderer = new ListCellRenderer() {
    Yeah yeah = new Yeah(false);
    Oh oh = new Oh();

    @Override
    public Component getListCellRendererComponent(JList list,
            Object value, int index, boolean isSelected,
            boolean cellHasFocus) {
        // ignore the given selection index, query the list instead
        if (list != null) {
            isSelected = list.isSelectedIndex(index);
        }
        if (isSelected || ((Integer) value) == 42) {
            yeah.isSelected = isSelected;
            return yeah;

        }
        return oh;
    }
};
list.setCellRenderer(renderer);
Run Code Online (Sandbox Code Playgroud)

要修复第二个问题,自定义ui委托(也可以在其他答案中建议)是一种可能的解决方案.虽然有些工作在一般情况下,但是如果需要支持多个LAF.

强制ui自动更新其缓存的一种不那么干扰但稍微脏的方法是在selectionChange上发送一个假的ListDataEvent:

ListSelectionListener l = new ListSelectionListener() {
    ListDataEvent fake = new ListDataEvent(list, ListDataEvent.CONTENTS_CHANGED, -1, -1);
    @Override
    public void valueChanged(ListSelectionEvent e) {
        JList list = (JList) e.getSource();
        ListDataListener[] listeners = ((AbstractListModel) list.getModel())
                .getListDataListeners();
        for (ListDataListener l : listeners) {
            if (l.getClass().getName().contains("ListUI")) {
                l.contentsChanged(fake);
                break;
            }
        }
    }
};
list.addListSelectionListener(l);
Run Code Online (Sandbox Code Playgroud)

顺便说一句,SwingX项目的JXList 有一个自定义ui委托 - 主要用于支持排序/过滤 - 用公共api重新计算缓存,然后上面的ListSelectionListener将被简化(并且干净:-)到

    ListSelectionListener l = new ListSelectionListener() {
        @Override
        public void valueChanged(ListSelectionEvent e) {
            ((JXList) e.getSource()).invalidateCellSizeCache();
        }
    };
    list.addListSelectionListener(l);
Run Code Online (Sandbox Code Playgroud)


And*_*ndy 5

我刚刚实现了这个功能.问题是,细胞渲染器被要求两次渲染细胞.在第一轮中,所有列表条目都在没有选择的情况下呈现,然后使用选择再次呈现所选单元格.因此,如果您在第一轮中提供首选大小,它将被缓存并用于第二轮.

诀窍是忽略中的isSelected布尔参数,getListCellRendererComponent并通过检查是否list.getSelectedIndices()包含给定索引来确定选择状态.

但是,我仍有问题,在列表可见后,渲染组件的高度有时会变大/变小.用鼠标调整列表大小后,一切都很好.我玩了验证/重新验证,重新绘制,重置缓存高度,但没有任何效果.摇摆有时候有点奇怪......