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:
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?
Basically, there are two aspects of the problem, both located in the ui delegate
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
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.
A less intrusive but slightly dirty method to force the ui into voluntarily update its cache is to send a fake ListDataEvent on selectionChange:
BTW, JXList of the SwingX project 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
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.
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:
Then when you create a JList - extend it like this :
You may need to put a guard around the triggerUpdate during creation depending on your circumstances.
Thanks to Rastislav Komara I've been able to solve this quite easily:
I've created an inner class that extends BasicListUI and created public method that is called on ListSelectionListener.valueChanged:
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.
Edit:
Check also the anser by nevster and kleopatra, they look way smarter, try them first...
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
isSelected
boolean parameter in thegetListCellRendererComponent
and to figure out the selection state by checking iflist.getSelectedIndices()
contains the given index.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...
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!
this is a simple solution:
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.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.
hope this can help you.