The Java platform's Java Foundation Classes/Swing (JFC/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.
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
|
|