2009-08-15 81 views
4

我正在制作一个自定义的ListCellRenderer。我知道你可以为每个单独的单元格设置不同的尺寸。但是现在我想为所选单元格设置不同的维度。不知何故,JList在第一次计算每个单元格的边界时缓存每个单元格的维度。 这是我的代码:Java Swing:带有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); 
    } 
} 

在评论你可以看到我已经试过了。

我已经搜索了很长时间,发现很多无用的文章,其中一些触摸ListCellRenderer /动态高度的东西,但他们只工作,因为单个单元格的高度保持不变。我的高度正在发生变化,那么我该怎么做?

回答

1

感谢拉斯蒂斯拉夫农产会社我已经能够解决这个问题很容易:

我已经创建了一个扩展BasicListUI并创建了被称为上ListSelectionListener.valueChanged公共方法内部类:

private class MyRenderer implements ListCellRenderer { 
    public int listSelectedIndex = -1; 

    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, 
      boolean cellHasFocus) { 
     if (index == listSelectedIndex) 
      return new Yeah(isSelected); 
     else 
      return new Oh(); 
    } 
} 
MyRenderer lcr = new MyRenderer(); 
private class MyListUI extends BasicListUI { 

    public void triggerUpdate() { 
     lcr.listSelectedIndex = list.getSelectedIndex(); 
     updateLayoutState(); 
     list.revalidate(); 
    } 
} 

updateLayoutState方法通常在JList高度更改时触发。 我在这里做的唯一“疯狂”事情是我的渲染器需要知道选定的索引是什么。这是因为updateLayoutState方法在其高度计算中不使用选定的索引。 以某种方式使用getListCellRendererComponent中的list.getSelectedIndex()不能正常工作。

编辑:
检查也nevster和克列奥帕特拉的雁,他们看起来更聪明的方式,尝试他们先......

+0

我的想法是,你以后得到OutofMemory错误,因为BasicListUI CellRendererPane缓存所有添加的组件(是和哦类)。 这个错误我得到了我的用例,最后我创建了BasicListUI的子类来删除所有以前注册的组件。 – 2013-07-03 11:11:55

0

JList可能会“缓存”您的单元格渲染器。尝试附加ListSelectionListener,并在选择更改时再次设置渲染器。

... 
addListSelectionListener(new ListSelectionListener() { 
    public void valueChanged(ListSelectionEvent event) { 
    if(event.getValueIsAdjusting() == false) { 
     list.setCellRenderer(new MyRenderer()); 
    } 
    } 
) 
... 
+0

这没有帮助.. – 2009-08-16 07:59:45

+0

这不起作用 – Jaap 2009-08-16 10:59:56

4

JList无法根据选择或其他来改变单元的大小。该列表使用“缓存”大小。如果提供了新的cellRenderer,则会在列表中的所有单元格中重新安装并应用这些大小。我认为原因是有很多条目的列表的性能。可能的解决方案是编写自己的ListUI实现,它能够为选定和未选定的单元使用不同的大小。这也带来了通过对数或其他内插来调整选择周围的细胞大小的可能性。我希望你有一个很大的理由,为什么要这样做。这是很多工作!

+0

感谢您指引我进入ListUI方向。我一直在吐槽BasicListUI实现,最后它并不难,尽管它是一个黑客! – Jaap 2009-08-16 12:42:19

2

我一直在撕裂我的头发,这个愚蠢的JList行高度问题。 我有一个单元格渲染器,为每一行设置变量行高 - 问题是JList保持高度缓存。

使用其他答案,我认为我已经击败了圣杯。那就是:

使用BasicListUI的简化版本由夏侯创建:

public class BetterListUI extends BasicListUI { 
    public void triggerUpdate() { 
     updateLayoutState(); 
    } 
} 

然后,当你创建一个JList - 扩展它是这样的:

betterListUI = new BetterListUI(); 
myJList = new JList() { 
    @Override 
    public void repaint(long tm, int x, int y, int width, int height) { 
     betterListUI.triggerUpdate(); 
     super.repaint(tm, x, y, width, height); 
    } 
}; 
myJList.setUI(betterListUI); 

您可能需要根据您的情况在创建期间在triggerUpdate周围放置一个警戒。

+1

看起来很有趣,我没有检查这个实现,因为它已经有一段时间了。 读者:我会在使用我之前尝试这种方法! – Jaap 2011-01-31 15:46:49

+0

它适合一种享受!使用它来创建一个iPad应用程序的原型... – nevster 2011-02-02 04:53:08

+0

为什么不扩展JList来覆盖重绘方法(而不是在每个实例中都不得不重写该代码)?此外,我还没有尝试,但如果你的问题只是触发JList再次布局它的组件 - 不会'SwingUtilities.updateComponentTreeUI(myJList);'工作? – Ozzy 2012-03-20 17:03:28

5

我刚刚实现了这个功能。问题是,单元格渲染器被询问两次渲染单元格。在第一轮中,所有列表条目都没有选择就被渲染,然后使用选择再次渲染选定的单元格。所以如果你在第一轮提供了一个首选大小,它将被缓存,并且也被用于第二轮。

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

但是,我仍然有问题,即在列表变得可见之后,渲染组件的高度有时会变大/变小。通过鼠标调整列表的大小后,再次正常。我玩弄验证/重新验证,重新绘制,重置缓存高度,但没有任何工作。 Swing是有时有点怪......

+0

+1好主意:-)看看我的答案,看看它是否能解决您剩下的问题。如果没有,请考虑发布一个单独的问题以及一些代码来重现问题,然后再看看它。 – kleopatra 2012-11-20 12:53:10

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

以固定第二,定制UI代理(如在其他答案建议作为孔)是一种可能的解决方案。虽然有些工作在一般情况下,如果需要支持多个LAF。

一个缺少强制的,但稍微脏方法来强制用户界面为主动更新其高速缓存为发送假的ListDataEvent上selectionChange:

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

BTW,JXList的SwingX项目有一个自定义UI委托 - 主要是用于支撑分类/过滤 - 与公共API来重新计算高速缓存中,则上述ListSelectionListener将被简化(和清洁:-)到

ListSelectionListener l = new ListSelectionListener() { 
     @Override 
     public void valueChanged(ListSelectionEvent e) { 
      ((JXList) e.getSource()).invalidateCellSizeCache(); 
     } 
    }; 
    list.addListSelectionListener(l); 
+0

我看不到任何差异,你是否记得你的commnent(ei)关于doLayout() – mKorbel 2012-11-20 13:42:05

+0

@mKorbel嗯...使用渲染器与直接查询选择状态(如@Andy建议)的组合并强制ui在OP中的ListSelectionListener中更新其缓存大小(通过假事件)SSCCE对我来说工作正常,即选定的行比未选中的要高 – kleopatra 2012-11-20 13:47:19

+0

不得不测试更大的模型,btw +1有点: - ) – mKorbel 2012-11-20 13:58:35

0

这是一个简单的解决方案:

public class VariableHeightListUI extends BasicListUI { 

    @Override 
    public void paint(Graphics g, JComponent c) { 
    updateLayoutState(); 
    super.paint(g, c); 
    } 
} 
当然,你需要编写自己的执行 ListCellRenderer,并根据列表元素的不同选择状态,您可以设置不同的

喜欢返回组件的高度。

只有一个问题需要进行:当您选择List FIRST时间的元素时,绘制不正确。但之后,一切运作良好。

希望这可以帮助你。

+0

我真的不认为paint()中的updateLayoutState()是一个非常好的主意(性能明智)。我可能会工作,但油漆不能保证。 – Jaap 2015-05-20 13:35:09