Java JLists: How to render images in JList cells

Here's another JList example, this time sharing some JList code I use to render images and text in JList cells.

Before going on, if you just want to display a set of images in a JList, all you have to do is create your images as an array Icons, and then add this array to your JList, and you're done. The JList is smart enough to render a Java Icon as an image, so in the most simple case, that's all you have to do.

However, in my case, because I want to render an image and text in each cell, I need to do a little more work. If you want to do something like this, read on.

Our JList/image example

Beginning with the end in mind, here's what our JList/image example will look like when it is displayed:

How to show images and text on JList cells

As you can see, this is a simple JList that displays three images, along with text next to those images. As you'll see in the code below, I create each cell as a JLabel adding an Icon to each JLabel, also setting the text on each JLabel.

(Bug report: I just noticed that I used the same image for the Ping and Netstat cells. I need to quit writing tutorials so late at night.)

Implement the ListCellRenderer interface

To render an image in a JList cell, you need to create a Java class that implements the ListCellRenderer interface, and then tell your JList to use this renderer with the JList setCellRenderer method.

Here's the source code for the Java class I've created that implements the ListCellRenderer interface, including plenty of Javadoc from the ListCellRenderer class:

class ImageListCellRenderer implements ListCellRenderer
{
  /**
   * From http://java.sun.com/javase/6/docs/api/javax/swing/ListCellRenderer.html:
   * 
   * Return a component that has been configured to display the specified value. 
   * That component's paint method is then called to "render" the cell. 
   * If it is necessary to compute the dimensions of a list because the list cells do not have a fixed size, 
   * this method is called to generate a component on which getPreferredSize can be invoked. 
   * 
   * jlist - the jlist we're painting
   * value - the value returned by list.getModel().getElementAt(index).
   * cellIndex - the cell index
   * isSelected - true if the specified cell is currently selected
   * cellHasFocus - true if the cell has focus
   */
  public Component getListCellRendererComponent(JList jlist, 
                                                Object value, 
                                                int cellIndex, 
                                                boolean isSelected, 
                                                boolean cellHasFocus)
  {
    if (value instanceof JPanel)
    {
      Component component = (Component) value;
      component.setForeground (Color.white);
      component.setBackground (isSelected ? UIManager.getColor("Table.focusCellForeground") : Color.white);
      return component;
    }
    else
    {
      // TODO - I get one String here when the JList is first rendered; proper way to deal with this?
      //System.out.println("Got something besides a JPanel: " + value.getClass().getCanonicalName());
      return new JLabel("???");
    }
  }
}

I can explain more about that class if anyone is interested, but for now I'll let it stand as is.

(Oops, I also need to figure out why this method is called once with a String value, and what I'm supposed to do in that instance. The two Sun examples I have seen both extend the JLabel class, so that seems to be the correct approach here.)

Set up the JList images

Once you have that ListCellRenderer created, all you need to do is (a) tell your JList that you want to use your ListCellRenderer with the setCellRenderer method, and (b) put your images on something like a JPanel and then add those panels to your JList with the setListData method.

The Java code below shows how I do this:

// construct the menuList as a JList
menuList = new JList();
menuList.setCellRenderer(new ImageListCellRenderer());

// get our images
Icon pingImage = new ImageIcon(MainFrame.class.getResource("images/server_from_client-32.png"));
Icon tracerouteImage = new ImageIcon(MainFrame.class.getResource("images/server_client_exchange-32.png"));
Icon netstatImage = new ImageIcon(MainFrame.class.getResource("images/server_from_client-32.png"));

// add the images to jlabels with text
JLabel pingLabel = new JLabel("Ping", pingImage, JLabel.LEFT);
JLabel tracerouteLabel = new JLabel("Traceroute", tracerouteImage, JLabel.LEFT);
JLabel netstatLabel = new JLabel("Netstat", netstatImage, JLabel.LEFT);

// create the corresponding panels
JPanel pingPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
JPanel traceroutePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
JPanel netstatPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));

// add the labels onto the panels
pingPanel.add(pingLabel);
traceroutePanel.add(tracerouteLabel);
netstatPanel.add(netstatLabel);

// create a panel array
Object[] panels = {pingPanel, traceroutePanel, netstatPanel};

// tell the jlist to use the panel array for its data
menuList.setListData(panels);

After this I do a little related JList setup work, like this:

menuList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
menuList.setLayoutOrientation(JList.VERTICAL);
menuList.setFixedCellHeight(46);

// put our JList in a JScrollPane
menuScrollPane = new JScrollPane(menuList);
menuScrollPane.setMinimumSize(new Dimension(150, 50));

// put our JList and JScrollPane in the left hand side of a JSplitPane
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, 
                           menuScrollPane, 
                           contentPanel);

JList / JLabel / image summary

While I need to clean up a couple of points there, I hope this JList image example is helpful. As usual, if you have any questions or comments, just let me know.

Also, for another good example of how to implement the ListCellRenderer interface, see the top of the ListCellRenderer Javadoc page. On that page they implement the ListCellRenderer and extend a JLabel, which I may do to simplify my code.