Customizing cell rendering

Swing's javax.swing.JList component and JavaFX's javafx.scene.control.ListView control let you customize how their various cells are rendered. In this post, I show you how to accomplish these tasks.

Customizing cell rendering in Swing JLists

Q: How do I customize how a JList instance's cells are rendered?

A: Swing provides the javax.swing.ListCellRenderer<E> interface to identify components that can be used as "rubber stamps" to render a JList instance's cells. This interface declares the following method:

Component getListCellRendererComponent(JList<? extends E> list,
                                       E value,
                                       int index,
                                       boolean isSelected,
                                       boolean cellHasFocus)

According to ListCellRenderer's Javadoc, getListCellRendererComponent() returns a java.awt.Component that's configured to display the specified value. The Component's paint() method is then called to render the cell.

getListCellRendererComponent() declares the following parameters:

  • list: references the JList instance whose cell is being rendered
  • value: the value returned from list.getModel().getElementAt(index)
  • index: the cell's index
  • isSelected: true when the specified cell is selected; otherwise, false
  • cellHasFocus: true when the specified cell has the focus; otherwise, false

You install a ListCellRenderer instance into a JList instance by calling JList's public void setCellRenderer(ListCellRenderer<? super E> cellRenderer) method.

Q: Can you provide an example showing how to customize the rendering of a JList instance's cells?

A: I've created an example that renders a country flag image and the name of a country in each JList cell. Listing 1 presents the example's Country class.

Listing 1. Country.java

import javax.swing.ImageIcon;

public class Country implements Comparable<Country>
{
   private ImageIcon flagIcon;

   private String name;

   private String path;

   public Country(String name, String path)
   {
      this.name = name;
      this.path = path;
   }

   public String getName()
   {
      return name;
   }

   public ImageIcon getFlagIcon()
   {
      // Lazily load flag icon. Make sure that each country's flag icon is 
      // loaded only once.

      if (flagIcon == null)
         flagIcon = new ImageIcon(path);

      return flagIcon;
   }

   @Override
   public int compareTo(Country o)
   {
      return name.compareTo(o.name);
   }

   @Override
   public String toString()
   {
      return name;
   }
}

Listing 1's Country class stores a country's name and the path to a file that stores an image of the country's flag. This information is stored by the constructor and returned by accessor methods getName() and getFlagIcon().

Country is used by the example's CountryCellRenderer and Countries classes. Listing 2 presents CountryCellRenderer, which is responsible for rendering each JList cell.

Listing 2. CountryCellRenderer.java

import java.awt.Color;
import java.awt.Component;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.ListCellRenderer;

import javax.swing.border.Border;

public class CountryCellRenderer extends JLabel 
                                 implements ListCellRenderer<Country>
{
   private Border border;

   CountryCellRenderer()
   {
      // Leave a 10-pixel separator between the flag icon and country name.

      setIconTextGap(10);

      // Swing labels default to being transparent; the container's color
      // shows through. To change a Swing label's background color, you must
      // first make the label opaque (by passing true to setOpaque()). Later,
      // you invoke setBackground(), passing the new color as the argument.

      setOpaque(true);

      // This border is placed around a cell that is selected and has focus.

      border = BorderFactory.createLineBorder(Color.RED, 1);
   }

   @Override
   public Component getListCellRendererComponent(JList<? extends Country> list,
                                                 Country value,
                                                 int index,
                                                 boolean isSelected,
                                                 boolean cellHasFocus)
   {
      setText(value.getName());
      setIcon(value.getFlagIcon());

      if (isSelected)

      {
         setBackground(list.getSelectionBackground());
         setForeground(list.getSelectionForeground());
      }
      else
      {
         setBackground(list.getBackground());
         setForeground(list.getForeground());
      }

      setFont(list.getFont());

      setEnabled(list.isEnabled());

      if (isSelected && cellHasFocus)
         setBorder(border);
      else
         setBorder(null);

      return this;
   }
}

Listing 2's CountryCellRenderer class extends javax.swing.JLabel, which simplifies the rendering of an image and text. getListCellRendererComponent() accomplishes these tasks by invoking JLabel's setText() and setIcon() methods on the country's name and icon, which are returned by invoking the value argument's getName() and getFlagIcon() methods.

After specifying the text and icon, cell selection is handled by specifying either the list's selection background and foreground colors or its normal background and foreground colors as the list's background and foreground colors.

Next, the list's font is assigned (we might assign a different font to the list), the rendering component is enabled or disabled to track the list's enabled state, and a special border is assigned to a selected and focused cell, to help it stand out.

Finally, the label component is returned and its paint() method is subsequently called (behind the scenes) to render the specified cell.

To complete this example, Listing 3 presents its Countries application class, which creates a suitable list-based user interface that responds to selection events by outputting country names to the standard output stream.

1 2 3 Page 1
Notice to our Readers
We're now using social media to take your comments and feedback. Learn more about this here.