定制JList的显示
Java平台的基本类(Java Foundation Classes)或者说是Swing 组件是一套图形界面的开发
包.使用Swing组件可让你的应用程序简单易用而不失丰富的表现,以此让你的应用程序具有
友好的用户界面.本文将着重说javax.swing.JList组件以及如何定制它的显示.
问题:
在图形界面上,javax.swing.JList显示成一个列表。默认时,在JList上仅显示一个对象的
toString()方法的返回值.比方说,你的应用程序里的JList上显示一串java.util.Locale对
象,用户通常在列表上选则一个选项来改变应用程序所用的语言环境.
想一想,JList是如何来显示一个包含Locale对象实例的数据模型的呢? JList是让
javax.swing.ListCellRenderer来显示这些对象的.正如你所想,ListCellRenderer将会
把对象的toString()方法的返回值显示出来.但是,Locale对象的实例返回的是ISO代码,
然而,这样的代码肯定不满足"友好的用户界面"的要求的.JList在默认情况下将显示出
如下图中的用户不知所云的那些东西.
http://java.sun.com/developer/technicalArticles/GUI/jlist/Figure1-DefaultDisplay.jpg
又一个例子也可说明在默认情况下,JList并不向终端用户显示任何有意义的数值.譬如,
你用的绘图程序会提供一个颜色选择的列表,你也许会从中选用一样颜色来填充某区域,
或者绘制一条某种颜色的线段.尽管把java.awt.Color的实例对象放进JList,这样做也是
非常有道理的,但是你会发现结果却是不实用.正如下图中那个放置在javax.swing.JFrame
右边的Color对象的列表.
http://java.sun.com/developer/technicalArticles/GUI/jlist/Figure2-Locale.jpg
Color对象的toString()方法返回的是颜色的RGB各个分量的亮度值的报告.除非你为显示
效果做点明显的区别外,用户是看不懂的.就拿上图中第三行被选中的那条来说:
255,200,0是橙黄色的RGB分量.默认情况下,你并不能直观地观察到颜色值和颜色的显示是否
正确.
当然,你也许会把java.lang.String对象的实例放进JList去替代Color对象实例.但是这么做
却放弃了使用JList的初衷.你的初衷是:用户是要从列表里选用一种颜色并非仅是选一段文本.
用Color对象的时候,要让JList的那些监听变化的监听器来返回一个实际看到的颜色.如果是
用String对象来替代的话,JList将把String传递到监听器,然后监听器去找到与这个String
对应的颜色来填充某区域或者是画一条这种颜色的线段.
如果是让用户来选颜色,把实际的Color对象实例放到JList中是对的.可是为了显示颜色,不得不
另做一些工作.JList的默认行为方式对于像Color或者Locale对象以及其他大多数对象来说并
不适合意想的要求.
解决办法
应用程序应当以友好的用户界面来显示数据,而不是把像Locale对象的ISO代码或者是颜色的RGB值
显示出来,这样才能贴近用户.ISO代码或者RGB值对编程的人来说或许有用,但是并不适合终端用户.
幸好,Locale对象有个displayName属性适合用来为用户显示信息.我们可用这个属性来替代
toString()方法用在JList的显示上,这样一来就能使得JList更具可读性.比较以下代码片段中Locale
对象的toString()方法和getDisplayName方法的返回值:
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());
}
以上代码在一台美国英语环境的机器的控制台上将显示出如下的结果:
toString getDisplayName
en_US English (United States)
fr_FR French (France)
th_TH Thai (Thailand)
es_MX Spanish (Mexico)
ja_JP Japanese (Japan)
displayName属性对用户来说更具可读性,也更贴近用户.如果应用程序里的JList使用
displayName,那么看起来会类似下图中的样子
http://java.sun.com/developer/technicalArticles/GUI/jlist/Figure3-userfriendly.jpg
这个效果是怎么实现的呢? 就如前面的简述中提到过用ListCellRenderer来显示文本.
为了让列表达到友好的用户界面的要求,关键是创建你自己的renderer.如上的例子里就用
displayName来替代默认的toString()方法的返回值.
类似地,如果你的应用程序中的JList是放颜色的,也可以定制renderer来显示实际的颜色或
者是显示颜色的名称.下图给出了一个颜色列表的样子
http://java.sun.com/developer/technicalArticles/GUI/jlist/Figure4-colorlist.jpg
javax.swing.DefaultListCellRenderer继承于javax.swing.JLabel并且实现了
ListCellRenderer接口.从先前的例子来看,适合的解决方案包括继承DefaultListCellRenderer
和实现ListCellRenderer接口里的getListCellRendererComponent方法.虽然自定义的
ListCellRenderer可继承自任何Component,还是用DefaultListCellRender的好,因为它继承
自JLabel并且还为设置文本,颜色甚至是图片提供了便利途径.必须实现的一个重要方法是:
public Component getListCellRendererComponent(JList list, Object value,
int index,boolean isSelected, boolean cellHasFocus)
为了改进Locale的显示效果,你需要像以下代码那样扩展DefaultListCellRenderer:
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;
}
}
如上代码片段中的装饰器先调用它的超类的getListCellRendererComponent()方法来绘制出组件,
而后仅简单的作了点贡献:用被选中的Locale对象的getDisplayName()方法的返回值来设置文本.
那么如何让JList来使用这个新的装饰器呢?很简单,调用setCellRenderer()方法并且把新创建的
ListCellRenderer作参数传递进去.现在,JList将用定制的装饰器来展现列表里每个Locale对象.
ListCellRenderer localeRenderer = new LocaleRenderer();
localeList.setCellRenderer(localeRenderer);
使用类似的方式来展现颜色对象.同样,我们还需要定制装饰器,还是简单地来扩展
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"},
};
}
本例与Locale的例子有点不同.不同之处在于,装饰器不仅设置了选项格子的文本,还设置了与颜色
对应的背景色.因为Color对象本身里没有内建的文本名,所以你需要把颜色名与颜色关联起来.
如上代码片段里,用HashMap类创建一个Color对象到String对象的映射.实例化这个装饰器前,应初
始化HashMap.这样才能使得HashMap在后来调用getListCellRendererComponent()方法时有实际作用.
总结
最后说一下对象如何在JList里显示.你不必靠对象提供一个实用的toString()方法,因为你可以
用ListCellRenderer来显示你想要和对象相关的任何文本.此外,在你选用来充当ListCellRenderer的
组件上可选用任何颜色或者绘制你想要的图象.这样的装饰器也能很好地用于javax.swing.JComboBox.
用定制的ListCellRenderer,使JList或JComboBox上的文本更趋于界面友好的要求.