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
- Creating a GUI with JFC/Swing (also known as the Swing Tutorial)
- The 2D Graphics Trail
- Internationalization: Understanding Locale in the Java Platform