Communities
|
Social Applications
Networks
Support
|
|
C-Level Executives
Other Roles
|
|
Support
Education
Partner
Other Tasks
|
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.
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.
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.
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.
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.
Download the source code used in this article.

