views:

1730

answers:

3

Hi all,

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!?!??

A: 

The JList is probably "caching" your cell renderer. Try to attach a ListSelectionListener, and set the renderer again when selection is changed.

...
addListSelectionListener(new ListSelectionListener() {  
  public void valueChanged(ListSelectionEvent event) { 
    if(event.getValueIsAdjusting() == false) {
      list.setCellRenderer(new MyRenderer());
    }
  }
)
...
Zed
This won't help..
Rastislav Komara
This doesn't work
Jaap
+3  A: 

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!

Rastislav Komara
Thanks for pointing me into the ListUI direction. I've been spitting in the BasicListUI implementation and in the end it's not difficult, though it's a hack!
Jaap
+1  A: 

Thx 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:

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.

Jaap