JFC/Swing活學活用系列之定制JList顯示
下載本文源代碼
引言
在圖形用戶界面方面(GUI),Java一直無法與C++、PB、Delphi之類抗衡,使用早期Java/AWT包所開發的界面實在是讓人不敢恭維。隨著JFC/Swing的日趨成熟,開發出與可以與C++媲美的GUI不再是"不可能完成的任務"。除了具有豐富的界面組件之外,以下優秀的特性更讓Swing如虎添翼。Swing是以100%純Java實現的,且輔以JDK 1.1 輕量級UI框架為基礎,沒有本地代碼,獨立于操作系統之外,基于MVC設計模式、采用可插入的外觀感覺(PL&F),這都讓我們忍不住去感受一下她的魅力。
完整的JFC十分巨大,Swing只是其中的一部分,本文將著重于Swing包中的JList組件以及如何定制顯示。
JList基礎知識
由JList類代表的Swing列表顯示一個可選取對象列表,它支持三種選取模式:單選取、單間隔選取、多間隔選取。
基于MVC的構建思想,JList類不維護對它所顯示的那些對象的引用,而是把數據管理工作委托給一個實現ListModel接口的對象;JList類不對其顯示對象的選取操作進行跟蹤和維護,而是把選取管理的工作委托給一個實現ListSelectionModel接口的對象;JList類不繪制其所顯示的對象,而是把列表單元的繪制工作委托給一個實現ListCellRenderer接口的對象。
JList組件把三個主要的工作(數據處理、列表項選取、單元繪制)交給其它對對象來完成,JList的每個實例都維護對上述對象的引用,這樣大大降低了各個功能模塊的耦合度,易于擴展和維護。
JList如何顯示對象列表?
缺省情況下,JList對象對圖標和字符串對象會按照原樣顯示,而對于其它所有對象只顯示對象toString()方法的返回值。舉個例子吧,有這樣一個應用程序顯示java.util.Locale對象列表給用戶,用戶可以通過選擇列表中的項目來改變應用程序所的處的語言環境。
想象一下,JList將如何顯示一個包含了Locale對象的數據模型呢?JList委派javax.swing.ListCellRenderer來顯示這些對象。正如我們所料,ListCellRenderer將把對象toString()方法的返回值顯示出來。然而,Locale對象返回的是ISO代碼,這樣的顯示肯定是不符合"界面交互友好"原則的。默認情況下JList所顯示的內容讓大多數用戶感到莫名其妙,如下圖所示:
再來看下面這個例子,更恰如其分地說明了JList對象的缺省顯示并不能對用戶提供任何有意義的數據。假設一個繪圖程序提供了顏色選擇列表,也許你會從中選擇一種顏色用于填充,也許用于繪制線條,或者干什么都可以。盡管我們將java.awt.Color對象實例放入JList中的作法是中規中矩的,但用戶卻不能從中得到任何的幫助,這背離了我們的初衷。如下圖所示:
Color對象toString()方法返回的是三原色紅、綠、藍(RGB)各分量的亮度值,而不論它所表現的具體顏色是什么。除非用戶知道所選取的第六行0、255、0所代表的是綠顏色,要么我們就應該在這個位置顯示一些對用戶更有幫助的信息。
誠然,你也許可以把java.lang.String對象的實例放入JList中以替代Color對象的實例,但這么做卻放棄了使用JList的目的:用戶是要從列表中選用一種顏色并非是選取一段文本描述。
當使用Color對象時,JList的監聽變化的監聽器返回的是用戶實際看到的顏色。如果用String對象來替代的話,JList將把String對象返回到監聽器,然后監聽器再去匹配與之對應的顏色來完成填充操作,有點多此一舉的感覺。
尋求解決方案
作為用戶,我們希望的應用程序應該是界面交互清晰明了的,而不是把Locale對象的ISO代碼或者是顏色的RGB值顯示出來,界面交互友好的軟件才能吸引用戶。ISO代碼或者RGB值對編程的人來說或許有用,但并不適合于終端用戶。
幸運的是,Locale對象有個displayName屬性適合用來為用戶顯示信息。我們可用該屬性來替代toString()方法用于JList的顯示,這樣一來就能使得JList更具可讀性。比較以下代碼片段中Locale對象的toString()方法和getDisplayName方法的返回值:
在將有中文Windows XP的機器上,運行結果如下所示:
toString getDisplayName
-------- --------------
en_US 英文 (美國)
fr_FR 法文 (法國)
th_TH 泰文 (泰國)
es_MX 西班牙文 (墨西哥)
ja_JP 日文 (日本)
Locale對象的displayName屬性對用戶來說更具可讀性,也更貼近用戶。如果應用程序里的JList使用displayName屬性,那么看起來會是下面的樣子:
那么這個效果是怎么實現的呢?為了讓列表在用戶界面上有更好的表現,我們要創建自己的ListCellRenderer,這樣在上面的例子中就可以通過displayName屬性來替代默認的toString()方法的返回值。
類似地,如果我們的選擇顏色的應用程序,我們也可以用定制的ListCellRenderer來顯示Color對象對應的名稱和它們的顏色。如下圖所示:
我們先來了解一下ListCellRenderer的工作原理,ListCellRenderer接口只定義了一個方法,該方法返回一個組件:
由getListCellRendererComponent返回的組件的作用就像一個像皮圖章,它把這個組件繪制到列表中列表項所占的區域。要注意的一點是,列表單元并不包含這個組件,這個組件只是繪制到列表單元上。這是很重要的,因為不能操縱這個組件,只能使用這個組件的可見代表來繪制列表單元。
缺省情況下,JList的實例配備一個繪制器,它是ListCellRenderer接口的一個簡單實現,即DefaultListCellRenderer類。該類擴展了JLabel類,而且可以顯示字符串或圖標,但不能在一個單元中同時顯示字符串和圖標。
雖然自定義的ListCellRenderer可繼承任何Component,但對于上述應用我們選取的解決方案還是使用DefaultListCellRenderer的好,因為繼承了JLabel,可以方便的設置文本、顏色,甚至圖片。參考下面這段代碼:
renderer先調用它的超類的getListCellRendererComponent()方法來繪制組件,接下來只需要進行一些簡單的設置就可以了,這里我們使用被選中的Locale對象的getDisplayName()方法的返回值來設置文本。
有了定制的ListCellRenderer,讓JList來使用這個新的renderer就更簡單了,調用JList對象的setCellRenderer()方法并且把新創建的ListCellRenderer實例作為參數傳遞進去,就足夠了。JList對象將用定制的renderer來展現列表里每個Locale對象。參考下面的代碼片段:
定制Color選取器的例子與Locale的例子有點不同。不同之處在于,裝飾器不僅設置了選項單元的文本內容,還設置了它的顏色與對應的背景色。因為Color對象本身里沒有內建的文本名,所以我們需要在顏色名和顏色之間建立映射關系。在這里我們使用HashMap來完成映射操作。具體代碼實例請見參考資料。
結束語
最后再提一下對象在JList中是如何顯示的。不必非得依賴對象提供的toString()方法,因為我們可以用ListCellRenderer來顯示任何想要顯示的和對象相關的文本。此外,我們也可以在選取的作為ListCellRenderer的組件上選用任何顏色或圖形來繪制。我們也可以將同樣的繪制器應用于JComboBox。使用定制的ListCellRenderer,可以使用JList和JComboBox組件來編寫用戶界面更加友好的應用程式。
引言
在圖形用戶界面方面(GUI),Java一直無法與C++、PB、Delphi之類抗衡,使用早期Java/AWT包所開發的界面實在是讓人不敢恭維。隨著JFC/Swing的日趨成熟,開發出與可以與C++媲美的GUI不再是"不可能完成的任務"。除了具有豐富的界面組件之外,以下優秀的特性更讓Swing如虎添翼。Swing是以100%純Java實現的,且輔以JDK 1.1 輕量級UI框架為基礎,沒有本地代碼,獨立于操作系統之外,基于MVC設計模式、采用可插入的外觀感覺(PL&F),這都讓我們忍不住去感受一下她的魅力。
完整的JFC十分巨大,Swing只是其中的一部分,本文將著重于Swing包中的JList組件以及如何定制顯示。
JList基礎知識
由JList類代表的Swing列表顯示一個可選取對象列表,它支持三種選取模式:單選取、單間隔選取、多間隔選取。
基于MVC的構建思想,JList類不維護對它所顯示的那些對象的引用,而是把數據管理工作委托給一個實現ListModel接口的對象;JList類不對其顯示對象的選取操作進行跟蹤和維護,而是把選取管理的工作委托給一個實現ListSelectionModel接口的對象;JList類不繪制其所顯示的對象,而是把列表單元的繪制工作委托給一個實現ListCellRenderer接口的對象。
JList組件把三個主要的工作(數據處理、列表項選取、單元繪制)交給其它對對象來完成,JList的每個實例都維護對上述對象的引用,這樣大大降低了各個功能模塊的耦合度,易于擴展和維護。
JList如何顯示對象列表?
缺省情況下,JList對象對圖標和字符串對象會按照原樣顯示,而對于其它所有對象只顯示對象toString()方法的返回值。舉個例子吧,有這樣一個應用程序顯示java.util.Locale對象列表給用戶,用戶可以通過選擇列表中的項目來改變應用程序所的處的語言環境。
想象一下,JList將如何顯示一個包含了Locale對象的數據模型呢?JList委派javax.swing.ListCellRenderer來顯示這些對象。正如我們所料,ListCellRenderer將把對象toString()方法的返回值顯示出來。然而,Locale對象返回的是ISO代碼,這樣的顯示肯定是不符合"界面交互友好"原則的。默認情況下JList所顯示的內容讓大多數用戶感到莫名其妙,如下圖所示:
![]() |
再來看下面這個例子,更恰如其分地說明了JList對象的缺省顯示并不能對用戶提供任何有意義的數據。假設一個繪圖程序提供了顏色選擇列表,也許你會從中選擇一種顏色用于填充,也許用于繪制線條,或者干什么都可以。盡管我們將java.awt.Color對象實例放入JList中的作法是中規中矩的,但用戶卻不能從中得到任何的幫助,這背離了我們的初衷。如下圖所示:
![]() |
Color對象toString()方法返回的是三原色紅、綠、藍(RGB)各分量的亮度值,而不論它所表現的具體顏色是什么。除非用戶知道所選取的第六行0、255、0所代表的是綠顏色,要么我們就應該在這個位置顯示一些對用戶更有幫助的信息。
誠然,你也許可以把java.lang.String對象的實例放入JList中以替代Color對象的實例,但這么做卻放棄了使用JList的目的:用戶是要從列表中選用一種顏色并非是選取一段文本描述。
當使用Color對象時,JList的監聽變化的監聽器返回的是用戶實際看到的顏色。如果用String對象來替代的話,JList將把String對象返回到監聽器,然后監聽器再去匹配與之對應的顏色來完成填充操作,有點多此一舉的感覺。
尋求解決方案
作為用戶,我們希望的應用程序應該是界面交互清晰明了的,而不是把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%s", "toString", "getDisplayName"); System.out.printf("%-10s%s", "--------", "--------------"); for (Locale l : locales) { System.out.printf("%-10s%s", l.toString(), l.getDisplayName()); } |
在將有中文Windows XP的機器上,運行結果如下所示:
toString getDisplayName
-------- --------------
en_US 英文 (美國)
fr_FR 法文 (法國)
th_TH 泰文 (泰國)
es_MX 西班牙文 (墨西哥)
ja_JP 日文 (日本)
Locale對象的displayName屬性對用戶來說更具可讀性,也更貼近用戶。如果應用程序里的JList使用displayName屬性,那么看起來會是下面的樣子:
![]() |
那么這個效果是怎么實現的呢?為了讓列表在用戶界面上有更好的表現,我們要創建自己的ListCellRenderer,這樣在上面的例子中就可以通過displayName屬性來替代默認的toString()方法的返回值。
類似地,如果我們的選擇顏色的應用程序,我們也可以用定制的ListCellRenderer來顯示Color對象對應的名稱和它們的顏色。如下圖所示:
![]() |
我們先來了解一下ListCellRenderer的工作原理,ListCellRenderer接口只定義了一個方法,該方法返回一個組件:
| Public abstract Component getListCellRendererComponent(JList list,Object value,Int index,boolean isSelected boolean cellHasFocus) |
由getListCellRendererComponent返回的組件的作用就像一個像皮圖章,它把這個組件繪制到列表中列表項所占的區域。要注意的一點是,列表單元并不包含這個組件,這個組件只是繪制到列表單元上。這是很重要的,因為不能操縱這個組件,只能使用這個組件的可見代表來繪制列表單元。
缺省情況下,JList的實例配備一個繪制器,它是ListCellRenderer接口的一個簡單實現,即DefaultListCellRenderer類。該類擴展了JLabel類,而且可以顯示字符串或圖標,但不能在一個單元中同時顯示字符串和圖標。
雖然自定義的ListCellRenderer可繼承任何Component,但對于上述應用我們選取的解決方案還是使用DefaultListCellRenderer的好,因為繼承了JLabel,可以方便的設置文本、顏色,甚至圖片。參考下面這段代碼:
| 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; } |
renderer先調用它的超類的getListCellRendererComponent()方法來繪制組件,接下來只需要進行一些簡單的設置就可以了,這里我們使用被選中的Locale對象的getDisplayName()方法的返回值來設置文本。
有了定制的ListCellRenderer,讓JList來使用這個新的renderer就更簡單了,調用JList對象的setCellRenderer()方法并且把新創建的ListCellRenderer實例作為參數傳遞進去,就足夠了。JList對象將用定制的renderer來展現列表里每個Locale對象。參考下面的代碼片段:
| ListCellRenderer localeRenderer = new LocaleRenderer(); localeList.setCellRenderer(localeRenderer); |
定制Color選取器的例子與Locale的例子有點不同。不同之處在于,裝飾器不僅設置了選項單元的文本內容,還設置了它的顏色與對應的背景色。因為Color對象本身里沒有內建的文本名,所以我們需要在顏色名和顏色之間建立映射關系。在這里我們使用HashMap來完成映射操作。具體代碼實例請見參考資料。
結束語
最后再提一下對象在JList中是如何顯示的。不必非得依賴對象提供的toString()方法,因為我們可以用ListCellRenderer來顯示任何想要顯示的和對象相關的文本。此外,我們也可以在選取的作為ListCellRenderer的組件上選用任何顏色或圖形來繪制。我們也可以將同樣的繪制器應用于JComboBox。使用定制的ListCellRenderer,可以使用JList和JComboBox組件來編寫用戶界面更加友好的應用程式。
編輯推薦:在Java中使用JGraph實現圖形繪制 |



