原文作者:John O'Conner
譯者註釋:
文中的某些代碼片段需要jdk1.5.0及之上的版本才支持.譬如
System.out.printf("%-10s/t%s/n", "--------", "--------------");
定製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在默認情況下將顯示出
如下圖中的用戶不知所云的那些東西.
又一個例子也可說明在默認情況下,JList並不向終端用戶顯示任何有意義的數值.譬如,
你用的繪圖程序會提供一個顏色選擇的列表,你也許會從中選用一樣顏色來填充某區域,
或者繪製一條某種顏色的線段.儘管把java.awt.Color的實例對象放進JList,這樣做也是
非常有道理的,但是你會發現結果卻是不實用.正如下圖中那個放置在javax.swing.JFrame
右邊的Color對象的列表.
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,那麼看起來會類似下圖中的樣子
這個效果是怎麼實現的呢? 就如前面的簡述中提到過用ListCellRenderer來顯示文本.
爲了讓列表達到友好的用戶界面的要求,關鍵是創建你自己的renderer.如上的例子裏就用
displayName來替代默認的toString()方法的返回值.
類似地,如果你的應用程序中的JList是放顏色的,也可以定製renderer來顯示實際的顏色或
者是顯示顏色的名稱.下圖給出了一個顏色列表的樣子
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上的文本更趨於界面友好的要求.
作者:hardneedl