Customize Your JList Display

by John O'Conner, November 2005

The Java platform's Swing components are a complete package of graphical user interface (GUI) widgets. By using Swing components, you can create rich, easy-to-use GUIs in your applications. Using these components can greatly improve your application's user-friendliness. This article focuses on one component, the javax.swing.JList object, and shows you how to customize what it displays to the user.

The Problem

A javax.swing.JList object displays a list of objects in a GUI. It doesn't display everything about those objects. Instead, by default, it displays the returned value of the object's toString method. Imagine that your application displays a list of java.util.Locale objects to your customer. In a sophisticated application, selecting from this list could change the application's user interface language.

Consider how JList displays a data model containing Locale objects. The JList delegates the display of these objects to a javax.swing.ListCellRenderer. As expected, the ListCellRenderer will display the text returned from the object's toString method. However, Locale objects return ISO codes, and these codes aren't especially user-friendly. The default behavior of JList displays something that most customers will not understand, as Figure 1 shows.


Figure 1. The Default Locale Display

Here is another example of how the default behavior of JList doesn't provide anything of significant value to the end user in its display. Imagine that your drawing application provides a list of colors. Presumably, you can use this color list to fill shapes or draw colored lines. Although putting java.awt.Color objects into a JList is a perfectly reasonable use of JList, most people will not find the displayed results helpful. In Figure 2, a list of Color objects is in the right ( BorderLayout.EAST) side of the javax.swing.JFrame.


Figure 2. The Default Color Display

A Color object's toString method reports the red, green, and blue (RGB) color intensities of whatever color it represents. Unless your customers know that the third line's values -- 255, 200, 0 -- are the RGB values of the color orange, you should display something different here. The color name or the color itself seems appropriate in a color list, but you won't get that by default.

Sure, you could place java.lang.String objects into the list instead of the actual Color objects themselves. However, this defeats the purpose of the JList: You want your user to pick a color -- not just a string of text -- from the list.

Using a Color object, your list returns an actual Color to your list change listeners. If you use a String instead, the list would return a String to listeners, and then the listeners would have to map that value to an actual Color in order to fill a shape or draw a colored line.

If the user is selecting colors, then it makes sense to put actual Color objects in the list. However, as you can see, we have to do something about the display of those colors. The default behavior isn't acceptable for Color or Locale objects, nor will it be useful for most other objects.

The Solution

Instead of the terse ISO codes for Locale or the RGB values for Color, your application should display something more user-friendly, something the customer will expect and find more familiar. ISO or RGB values, although probably helpful to a programmer, are not typically helpful to an end user.

Fortunately, Locale also has a displayName property suitable for displaying to customers. If we could somehow coerce JList to use that property instead of toString, we could create a more readable list. Compare the different values returned from Locale's toString and getDisplayName methods in the following code snippet:

Locale[] locales = {new Locale("en", "US"),
                     new Locale("fr", "FR"),
                     new Locale("th", "TH"), new Locale("es", "MX"),   
                     new Locale("ja", "JP")};          
                     System.out.printf("%-10s\t%s\n",
                     "toString", "getDisplayName"); 
                     System.out.printf("%-10s\t%s\n",
                     "--------", "--------------");
                     for(Locale l: locales) { 
                     System.out.printf("%-10s\t%s\n",
                     l.toString(),
                     l.getDisplayName());
                     }

The above code generates the following console output for a host in a default en_US locale:

toString        getDisplayName --------    
    -------------- en_US           English (United States) fr_FR 
    French (France) th_TH           Thai (Thailand) es_MX     
    Spanish (Mexico) ja_JP           Japanese (Japan)

The displayName is much more readable and probably more useful to your customers. If your application's JList could use displayName instead, it would look much like Figure 3.


Figure 3. The User-Friendly Locale List

How do we do this? As briefly mentioned earlier, the list uses a ListCellRenderer to display text. The key to getting more user-friendly information in the list is to create your own renderer, one that uses displayName instead of the default toString value.

Similarly, if our application stores colors, we can use a customized renderer to display the color's name or the color itself. Figure 4 shows an example of a list of colors.


Figure 4. The User-Friendly Color List

ListCellRenderer is an interface. The javax.swing.DefaultListCellRenderer class extends javax.swing.JLabel and implements this interface. For both of these examples, a reasonable solution involves extending DefaultListCellRenderer and implementing the interface method getListCellRendererComponent. Although your ListCellRenderer can extend any Component, it is convenient to use DefaultListCellRender because it extends JLabel and provides an easy way to set the text, color, and even an image. The important method that you must implement is this:

public Component getListCellRendererComponent(JList list,
  
  Object value,int index,boolean isSelected,
  boolean cellHasFocus)

To create the improved Locale display, you must extend DefaultListCellRenderer like this:

package com.sun.demo.cellrenderer; 
import javax.swing.DefaultListCellRenderer; 
import javax.swing.JList; import java.util.Locale;
 import java.awt.Component; 
 public class LocaleRenderer extends DefaultListCellRenderer {
 /** Creates a new instance of LocaleRenderer */   public LocaleRenderer(){   } 
 public Component getListCellRendererComponent(JList list,
 Object value, int index, boolean isSelected, 
 boolean cellHasFocus) { 
 super.getListCellRendererComponent(list, value,index,isSelected,cellHasFocus);  
 Locale l = (Locale)value; 
 setText(l.getDisplayName());  
 return this;   } }

This renderer calls its superclass to draw the entire component and then does only one simple thing to contribute: It sets the component's text to the value returned by the selected Locale object's getDisplayName method.

How do you tell the JList to use this new renderer? Simple. Call its setCellRenderer method and pass in the newly created ListCellRenderer. Now the list will use the customized renderer to represent every Locale object in the JList.

package com.sun.demo.cellrenderer ListCellRenderer 
localeRenderer = new LocaleRenderer();
 localeList.setCellRenderer(localeRenderer);

A similar solution exists for displaying Color objects. Again, we need a custom renderer, and again it makes sense to simply extend the DefaultListCellRenderer:

package com.sun.demo.cellrenderer; 
import javax.swing.ListCellRenderer; 
import javax.swing.JLabel; 
import javax.swing.DefaultListCellRenderer;
 import java.awt.Color; import javax.swing.JList; import java.awt.Component; 
 import java.util.HashMap; 
 public class ColorRenderer  extends DefaultListCellRenderer {
 /** Creates a new instance of ColorRenderer */  
 public ColorRenderer() {     initColorMap();   }  
 public Component getListCellRendererComponent(JList list, 
 Object value, 
 int index,  
 boolean isSelected,
 boolean cellHasFocus) { super.getListCellRendererComponent(list,
 value, index, isSelected, cellHasFocus);  
 if (value instanceof Color) {       Color color = (Color)value;
 String strColor = (String)colorMap.get(color); 
 if (strColor != null) {         setText(strColor); 
 }       setBackground(color);  
 }     return this; 
 }        private void initColorMap() {     colorMap = new HashMap();
 for (int x=0; x < colorAssociation.length; ++x) { 
 colorMap.put(colorAssociation[x][0], colorAssociation[x][1]);     }  
 colorAssociation = null; 
 }        private HashMap colorMap;  
 private Object[][] colorAssociation = {     {Color.BLACK, "Black" }, 
 {Color.BLUE, "Blue" },     {Color.CYAN, "Cyan" },    
 {Color.DARK_GRAY, "Dark Gray" },     {Color.GRAY, "Gray" }, 
 {Color.GREEN, "Green"},     {Color.LIGHT_GRAY, "Light Gray" }, 
 {Color.MAGENTA, "Magenta"},      {Color.ORANGE, "Orange" },   
 {Color.PINK, "Pink" },      {Color.RED, "Red"},    
 {Color.WHITE, "White"},      {Color.YELLOW, "Yellow"},   };   }

This example is a little different from the Locale example. For Color objects, the renderer will set the background color of its cell to match the Color object and set the text to the color name. Because the Color object doesn't have any internal text name, you have to associate a name with it. Do this by creating a mapping from a Color object to a String using a HashMap class as shown above. During the instantiation of this renderer, you initialize the HashMap. The HashMap is then available during subsequent calls to getListCellRendererComponent.

Summary

You have the final say in how objects are displayed in JList components. You don't have to depend on the object to provide a useful toString method because you can use a ListCellRenderer to display any text you want to associate with an object. Furthermore, you can use any color or draw any image you'd like in the Component you choose as your ListCellRenderer. You can use this same renderer in a javax.swing.JComboBox as well. Using a customized ListCellRenderer, you can make the displayed text in any JList or JComboBox more user-friendly.

For More Information