簡(jiǎn)介
開(kāi)放源碼 Eclipse 項(xiàng)目是 Java 領(lǐng)域中最有趣的新近開(kāi)發(fā)項(xiàng)目之一。Eclipse 把自己描述成“一種通用的工具平臺(tái) — 開(kāi)放的可擴(kuò)展 IDE,可用于任何用途且沒(méi)有特殊之處”。它的兩個(gè)主要組件是名為 SWT 的圖形庫(kù)和與其匹配的名為 JFace 的實(shí)用程序框架。
SWT 是一個(gè)窗口構(gòu)件集和圖形庫(kù),它集成于本機(jī)窗口系統(tǒng)但有獨(dú)立于 OS 的 API。
JFace 是用 SWT 實(shí)現(xiàn)的 UI 工具箱,它簡(jiǎn)化了常見(jiàn)的 UI 編程任務(wù)。JFace 在其 API 和實(shí)現(xiàn)方面都是獨(dú)立于窗口系統(tǒng)的,它旨在使用 SWT 而不隱藏它。圖 1 演示了 Eclipse、JFace 和 SWT 之間的關(guān)系。
Hello, World
讓我們從我能想到的最簡(jiǎn)單的 JFace 程序開(kāi)始,逐步擴(kuò)充它,將其構(gòu)建為最常見(jiàn)的“Hello, World”程序。
清單 1. Hello(版本 1)
import org.eclipse.jface.window.*;
import org.eclipse.swt.widgets.*;
public class Hello
{
public static void main(String[] args)
{
ApplicationWindow w = new ApplicationWindow(null);
w.setBlockOnOpen(true);
w.open();
Display.getCurrent().dispose();
}
}
這里我們創(chuàng)建了一個(gè)名為 Hello 的類(lèi),其中 main 方法僅僅創(chuàng)建了一個(gè) ApplicationWindow,然后打開(kāi)它。setBlockOnOpen() 使 open() 阻塞,直到窗口關(guān)閉為止。在窗口已關(guān)閉之后,我們獲取當(dāng)前的 Display 并除去它。這會(huì)釋放在操作系統(tǒng)中用到的資源。當(dāng)您運(yùn)行該程序時(shí),您會(huì)看到類(lèi)似圖 2 的窗口:
就是如此。它甚至沒(méi)有說(shuō)“Hello, World”。在修正它之前,讓我們把話(huà)題轉(zhuǎn)到 JFace 窗口。
JFace 應(yīng)用程序窗口
窗口是頂級(jí)窗口(換句話(huà)說(shuō),由 OS 窗口管理器管理的窗口)的 JFace 類(lèi)。JFace 窗口實(shí)際上不是頂級(jí)窗口的 GUI 對(duì)象(SWT 已經(jīng)提供了一個(gè),名為 Shell)。相反,JFace 窗口是助手對(duì)象,它知道對(duì)應(yīng)的 SWT Shell 對(duì)象,并提供代碼來(lái)幫助創(chuàng)建/編輯它,以及偵聽(tīng)它的事件等。圖 3 演示了您的代碼、JFace 和 SWT 之間的關(guān)系。

事實(shí)上,這一模型是理解 JFace 如何工作的關(guān)鍵。它并不真的是 SWT 之上的層,而且它沒(méi)有試圖向您隱藏 SWT。相反,JFace 意識(shí)到有幾種使用 SWT 的常用模式,而且它提供了一些實(shí)用程序代碼,以幫助您更方便地對(duì)這些模式編程。為了做到這一點(diǎn),JFace 提供可使用的對(duì)象,或提供可將其子類(lèi)化的類(lèi)(有時(shí)它兩者都提供)。
盡管我們僅僅直接使用了一個(gè) ApplicationWindow,但實(shí)際上它們被設(shè)計(jì)為可以子類(lèi)化也可以加入特定行為。它們有現(xiàn)成的菜單欄、工具欄、供您插入特定于應(yīng)用程序的內(nèi)容的區(qū)域和狀態(tài)欄 — 全都是可選的。圖 4 用 JFace File Explorer 示例本身演示了這些區(qū)域。

讓我們改進(jìn) Hello,使它成為 ApplicationWindow 的子類(lèi)。更改的行在清單 2 中突出顯示。
清單 2. Hello(版本 2)
import org.eclipse.jface.window.*;
import org.eclipse.swt.widgets.*;
public class Hello extends ApplicationWindow
{
public Hello()
{
super(null);
}
public static void main(String[] args)
{
Hello w = new Hello();
w.setBlockOnOpen(true);
w.open();
Display.getCurrent().dispose();
}
}
您編寫(xiě)的構(gòu)造函數(shù)必須調(diào)用超類(lèi)構(gòu)造函數(shù)(如往常一樣)。讓我們暫時(shí)不考慮該構(gòu)造函數(shù)的參數(shù)。運(yùn)行該程序的結(jié)果與前一個(gè)程序沒(méi)有任何不同。缺省情況下,程序不會(huì)為我們顯示任何裝飾性的東西。我們的程序要?jiǎng)?chuàng)建一個(gè)帶有文本“Hello, World”的按鈕。這個(gè)按鈕要顯示在內(nèi)容(Contents)區(qū)域。要做到這一點(diǎn),我們必須實(shí)現(xiàn) Control createContents(Composite parent) 方法。
ApplicationWindow 將在所有其它窗口構(gòu)件已經(jīng)創(chuàng)建之后但窗口在屏幕上顯示之前調(diào)用該方法。參數(shù) parent 是代表內(nèi)容區(qū)域的復(fù)合窗口構(gòu)件。這里的想法是您創(chuàng)建一個(gè)復(fù)合窗口構(gòu)件,將其添加到 parent,然后添加您的窗口構(gòu)件,并返回您創(chuàng)建的復(fù)合窗口構(gòu)件。圖 5 演示了實(shí)例層次結(jié)構(gòu)。

我們的內(nèi)容目前非常簡(jiǎn)單:parent 下的單一按鈕,如清單 3 所示。
清單 3. Hello(版本 3)
import org.eclipse.jface.window.*;
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
public class Hello extends ApplicationWindow
{
public Hello()
{
super(null);
}
protected Control createContents(Composite parent)
{
Button b = new Button(parent, SWT.PUSH);
b.setText("Hello World");
return b;
}
public static void main(String[] args)
{
Hello w = new Hello();
w.setBlockOnOpen(true);
w.open();
Display.getCurrent().dispose();
}
}
結(jié)果是圖 6

這就是我們要實(shí)現(xiàn)的。我們使用 JFace 創(chuàng)建的第一個(gè)“Hello, World”程序:包含單一按鈕的窗口。現(xiàn)在讓我們繼續(xù)討論文件資源管理器這一話(huà)題。首先,我們將創(chuàng)建顯示文件夾層次結(jié)構(gòu)的樹(shù)查看器。使用 TreeViewer 和 ApplicationWindow 一樣,TreeViewer 不是真正的 SWT 窗口構(gòu)件,它也沒(méi)有打算向您隱藏 SWT 窗口構(gòu)件。它使用 SWT 樹(shù)窗口構(gòu)件來(lái)顯示各項(xiàng),并且還使用許多其它對(duì)象來(lái)協(xié)助它。不象 ApplicationWindow,JFace TreeViewer 并不旨在被子類(lèi)化。
這里的想法是 TreeViewer 知道要顯示的樹(shù)的根元素。當(dāng)然,您必須告訴它那個(gè)對(duì)象是什么:TreeViewer: void setInput(Object rootElement)
為了開(kāi)始顯示,樹(shù)查看器向根元素請(qǐng)求子元素并顯示它們。然后,當(dāng)用戶(hù)展開(kāi)其中的一個(gè)子元素時(shí),樹(shù)查看器向該節(jié)點(diǎn)請(qǐng)求子元素,以此類(lèi)推。實(shí)際上,并不完全是那樣。TreeViewer 并不直接使用域?qū)ο?— 而是使用另一個(gè)名為 ContentProvider 的對(duì)象,這個(gè)對(duì)象才使用域?qū)ο螅鐖D 7 所示。

當(dāng)然,您必須實(shí)現(xiàn) ContentProvider。對(duì)于 TreeViewer,您的類(lèi)必須實(shí)現(xiàn) ITreeContentProvider 接口。實(shí)現(xiàn) TreeContentProvider
有六個(gè)方法需要實(shí)現(xiàn)。實(shí)際上我們不用做全部的工作,只需實(shí)現(xiàn)其中的三個(gè)就行,因此,本著“即時(shí)滿(mǎn)意(instant gratification)”的精神,讓我們暫時(shí)只考慮那幾個(gè)方法吧。下面的代碼演示了樹(shù)查看器如何向內(nèi)容提供程序請(qǐng)求正好位于根元素下的頂級(jí)元素:
ITreeContentProvider: public Object[] getElements(Object element)
隨后,每當(dāng)它需要特定元素的子元素時(shí),它就使用以下方法:
ITreeContentProvider: public Object[] getChildren(Object element)
為了知道某個(gè)節(jié)點(diǎn)是否有子元素(有的話(huà)會(huì)將小加號(hào)放到它旁邊),樹(shù)查看器只需請(qǐng)求該節(jié)點(diǎn)的子元素,然后會(huì)詢(xún)問(wèn)有多少子元素。萬(wàn)一您的代碼需要更快捷的方法來(lái)做到這一點(diǎn),則您必須實(shí)現(xiàn)另一個(gè)方法:
public boolean hasChildren(Object element)
正如您所見(jiàn),內(nèi)容提供程序不持有對(duì)任何域?qū)ο蟮囊谩3钟袑?duì)這些域?qū)ο蟮囊玫氖菢?shù)查看器本身,它把這些域?qū)ο笞鳛閰?shù)傳遞給內(nèi)容提供程序中的各個(gè)方法。在我們的例子中,節(jié)點(diǎn)是 File 對(duì)象。為獲取子元素,我們使用 listFiles()。我們必須記得要檢查 listFiles() 是否返回 null,然后使其變成空數(shù)組。為了獲取頂級(jí)元素(正好位于根元素之下),我們只需重用 getChildren() 方法。
getParent() 方法被用來(lái)實(shí)現(xiàn) reveal(Object element) 方法,后者使樹(shù)查看器滾動(dòng)其 SWT 樹(shù)窗口構(gòu)件,以便顯示樹(shù)中特定的節(jié)點(diǎn)。問(wèn)題是:如果此刻實(shí)際上并沒(méi)有顯示那個(gè)節(jié)點(diǎn),那么應(yīng)該在哪里顯示它?JFace 會(huì)尋找其父元素,以及父元素的父元素等等,直到它達(dá)到已顯示的節(jié)點(diǎn),然后它再次回頭尋找,直到目標(biāo)節(jié)點(diǎn)已顯示。
hasChildren() 方法只是做了顯而易見(jiàn)(未優(yōu)化)的事情,最后我們有了清單 4 中所示的代碼。
清單 4. FileTreeContentProvider(版本 1)
import java.io.*;
import java.util.*;
import org.eclipse.jface.viewers.*;
public class FileTreeContentProvider implements ITreeContentProvider
{
public Object[] getChildren(Object element)
{
Ob