Java Swing:JList 与 ListCellRenderer 选择不同高度的项目
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1282865/
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
Java Swing: JList with ListCellRenderer selected item different height
提问by Jaap
I'm making a custom ListCellRenderer. I know that you can have different dimensions for each individual cell. But now I want to have a different dimension for the selected cell. Somehow, the JList is caching the dimension for each individual cell the first time it has to calculate bounds for each cell. This is my code:
我正在制作一个自定义的 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);
}
}
In the comments you can see what I've already tried.
在评论中,您可以看到我已经尝试过的内容。
I've already searched quite long and found a lot of useless articles, some of them touch the ListCellRenderer/dynamic height thing, but they only work because the height stays the same for the individual cells. My heights are changing, so how do I do this?
我已经搜索了很长时间,发现了很多无用的文章,其中一些涉及 ListCellRenderer/动态高度的东西,但它们之所以有效,是因为单个单元格的高度保持不变。我的身高在变化,我该怎么做?
采纳答案by Jaap
Thanks to Rastislav Komara I've been able to solve this quite easily:
感谢 Rastislav Komara,我已经能够很容易地解决这个问题:
I've created an inner class that extends BasicListUI and created public method that is called on ListSelectionListener.valueChanged:
我创建了一个扩展 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();
}
}
The updateLayoutState method is normally triggered when the JList height changes. The only "insane" thing I'm doing here is that my renderer needs to know what the selected index is. This is because the updateLayoutState method doesn't use the selected index in it's height calculations. Somehow using list.getSelectedIndex() inside getListCellRendererComponent doesn't work well.
updateLayoutState 方法通常在 JList 高度发生变化时触发。我在这里做的唯一“疯狂”的事情是我的渲染器需要知道所选索引是什么。这是因为 updateLayoutState 方法在其高度计算中不使用选定的索引。不知何故在 getListCellRendererComponent 中使用 list.getSelectedIndex() 效果不佳。
Edit:
Check also the anser by nevster and kleopatra, they look way smarter, try them first...
编辑:
还要检查 nevster 和 kleopatra 的 anser,它们看起来更聪明,请先尝试...
回答by kleopatra
Basically, there are two aspects of the problem, both located in the ui delegate
基本上有两个方面的问题,都位于ui委托中
- it fails to configure the renderer to its realstate when measuring, that is ignores the selection (and focus) completely
- it is notoriously stubborn against being forced to re-calculate the cached cell sizes: it has no public api to do so and only does voluntarily on model changes.
- 测量时无法将渲染器配置为其真实状态,即完全忽略选择(和焦点)
- 众所周知,它顽固地反对被迫重新计算缓存的单元格大小:它没有公共 API 可以这样做,并且只会在模型更改时自愿执行。
The remedy to fix the first is indeed the renderer: implement to ignore the given selected flag and query the list for the real selection, as outlined by @Andy. In code, using the OP's components
修复第一个的补救措施确实是渲染器:实现忽略给定的选定标志并查询真正选择的列表,如@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);
To fix the second, a custom ui delegate (as suggested in others answers as well) is a possible solution. Though some work in the general case, if supporting multiple LAFs is needed.
要解决第二个问题,自定义 ui 委托(也如其他答案中所建议)是一种可能的解决方案。尽管在一般情况下有些工作,但如果需要支持多个 LAF。
A less intrusive but slightly dirty method to force the ui into voluntarily update its cache is to send a fake ListDataEvent on selectionChange:
强制用户界面自愿更新其缓存的一种侵入性较小但稍微有点脏的方法是在 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);
BTW, JXList of the SwingXproject has a custom ui delegate - mainly for supporting sorting/filtering - with public api to re-calculate the cache, then the above ListSelectionListener would be simplified (and clean :-) to
顺便说一句,SwingX项目的JXList有一个自定义的 ui 委托 - 主要用于支持排序/过滤 - 使用公共 api 重新计算缓存,那么上面的 ListSelectionListener 将被简化(和干净 :-) 到
ListSelectionListener l = new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
((JXList) e.getSource()).invalidateCellSizeCache();
}
};
list.addListSelectionListener(l);
回答by Andy
I just implemented this feature. The problem is, that the cell renderer is asked twice for rendering a cell. In the first round all list entries are rendered without selection, then the selected cells are rendered again using selection. So if you provide a preferred size in the first round, it is cached and also used for the second round.
我刚刚实现了这个功能。问题是,单元格渲染器被要求两次渲染单元格。在第一轮中,所有列表条目都在没有选择的情况下呈现,然后使用选择再次呈现选定的单元格。因此,如果您在第一轮中提供首选大小,它会被缓存并用于第二轮。
The trick is to ignore the isSelectedboolean parameter in the getListCellRendererComponentand to figure out the selection state by checking if list.getSelectedIndices()contains the given index.
诀窍是忽略 中的isSelected布尔参数getListCellRendererComponent并通过检查是否list.getSelectedIndices()包含给定索引来确定选择状态。
But, I still have the problem, that after the list is made visible, the height of the rendered components are sometimes to large/small. After resizing the list by mouse everything is fine again. I played around with validate/revalidate, repaint, reset of cached heights, but nothing worked. Swing is sometimes a bit strange...
但是,我仍然有问题,在列表可见后,渲染组件的高度有时会变大/变小。用鼠标调整列表大小后,一切又好了。我尝试了验证/重新验证、重新绘制、重置缓存高度,但没有任何效果。Swing 有时候有点奇怪...
回答by Rastislav Komara
The JList has no ability to change size of cell depending on selection or whatever. The list use "cached" sizes. If there is new cellRenderer provided this sizes are recounted and applied within all cells in list. I think the reason is performance for list with a lot of entries. The possible solution is to write own ListUI implementation which is able to use different sizes for selected and unselected cells. This brings also possibility to adjust size of cells around selection by logarithm or other interpolation. I hope you have a big reason why to do this. It is a lot of work!
JList 无法根据选择或其他方式更改单元格的大小。该列表使用“缓存”大小。如果有新的 cellRenderer,则重新计算此大小并将其应用于列表中的所有单元格。我认为原因是具有大量条目的列表的性能。可能的解决方案是编写自己的 ListUI 实现,它能够为选定和未选定的单元格使用不同的大小。这也带来了通过对数或其他插值调整选择周围单元格大小的可能性。我希望你有一个重要的理由为什么要这样做。这是很多工作!
回答by nevster
I've been tearing my hair out about this stupid JList row height problem. I have a cell renderer which sets a variable row height for every row - problem is that JList keeps a cache of the heights.
我一直在为这个愚蠢的 JList 行高问题而烦恼。我有一个单元格渲染器,它为每一行设置一个可变的行高 - 问题是 JList 保留了高度的缓存。
Using the other answers, I think I've struck on the holy grail. Here it is:
使用其他答案,我想我已经找到了圣杯。这里是:
Use a simplified version of the BasicListUI as created by Jaap:
使用 Jaap 创建的 BasicListUI 的简化版本:
public class BetterListUI extends BasicListUI {
public void triggerUpdate() {
updateLayoutState();
}
}
Then when you create a JList - extend it like this :
然后当你创建一个 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);
You may need to put a guard around the triggerUpdate during creation depending on your circumstances.
根据您的情况,您可能需要在创建过程中对 triggerUpdate 进行保护。
回答by Zed
The JList is probably "caching" your cell renderer. Try to attach a ListSelectionListener, and set the renderer again when selection is changed.
JList 可能正在“缓存”您的单元格渲染器。尝试附加一个 ListSelectionListener,并在选择更改时再次设置渲染器。
...
addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent event) {
if(event.getValueIsAdjusting() == false) {
list.setCellRenderer(new MyRenderer());
}
}
)
...
回答by Jufeng Yao
this is a simple solution:
这是一个简单的解决方案:
public class VariableHeightListUI extends BasicListUI {
@Override
public void paint(Graphics g, JComponent c) {
updateLayoutState();
super.paint(g, c);
}
}
of course you need write your own implementation of ListCellRenderer, and according to different selection state of list element, you can set different prefer height of returned Component.
当然你需要自己编写实现ListCellRenderer,并且根据列表元素的不同选择状态,可以设置不同的返回组件的首选项高度。
Only one issue need to go on is : when you select an element of List FIRST time, not draw correctly. but after then, all work well.
只有一个需要解决的问题是:当您第一次选择 List 的元素时,绘制不正确。但在那之后,一切正常。
hope this can help you.
希望这可以帮到你。

