簡介 開放源碼 Eclipse 項目是 Java 領域中最有趣的新近開發項目之一。Eclipse 把自己描述成“一種通用的工具平臺 — 開放的可擴展 IDE,可用于任何用途且沒有特殊之處”。它的兩個主要組件是名為 SWT 的圖形庫和與其匹配的名為 JFace 的實用程序框架。
SWT 是一個窗口構件集和圖形庫,它集成于本機窗口系統但有獨立于 OS 的 API。
JFace 是用 SWT 實現的 UI 工具箱,它簡化了常見的 UI 編程任務。JFace 在其 API 和實現方面都是獨立于窗口系統的,它旨在使用 SWT 而不隱藏它。圖 1 演示了 Eclipse、JFace 和 SWT 之間的關系。
圖 1. Eclipse Workbench、JFace 和 SWT Hello, World
讓我們從我能想到的最簡單的 JFace 程序開始,逐步擴充它,將其構建為最常見的“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();
}
}
這里我們創建了一個名為 Hello 的類,其中 main 方法僅僅創建了一個 ApplicationWindow,然后打開它。setBlockOnOpen() 使 open() 阻塞,直到窗口關閉為止。在窗口已關閉之后,我們獲取當前的 Display 并除去它。這會釋放在操作系統中用到的資源。當您運行該程序時,您會看到類似圖 2 的窗口:
圖 2. Hello(版本 2) 就是如此。它甚至沒有說“Hello, World”。在修正它之前,讓我們把話題轉到 JFace 窗口。
JFace 應用程序窗口 窗口是頂級窗口(換句話說,由 OS 窗口管理器管理的窗口)的 JFace 類。JFace 窗口實際上不是頂級窗口的 GUI 對象(SWT 已經提供了一個,名為 Shell)。相反,JFace 窗口是助手對象,它知道對應的 SWT Shell 對象,并提供代碼來幫助創建/編輯它,以及偵聽它的事件等。圖 3 演示了您的代碼、JFace 和 SWT 之間的關系。
圖 3. 您的代碼、JFace Window 和 SWT Shell 之間的關系 事實上,這一模型是理解 JFace 如何工作的關鍵。它并不真的是 SWT 之上的層,而且它沒有試圖向您隱藏 SWT。相反,JFace 意識到有幾種使用 SWT 的常用模式,而且它提供了一些實用程序代碼,以幫助您更方便地對這些模式編程。為了做到這一點,JFace 提供可使用的對象,或提供可將其子類化的類(有時它兩者都提供)。
盡管我們僅僅直接使用了一個 ApplicationWindow,但實際上它們被設計為可以子類化也可以加入特定行為。它們有現成的菜單欄、工具欄、供您插入特定于應用程序的內容的區域和狀態欄 — 全都是可選的。圖 4 用 JFace File Explorer 示例本身演示了這些區域。
圖 4. 應用程序窗口的各個部分 讓我們改進 Hello,使它成為 ApplicationWindow 的子類。更改的行在清單 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();
}
}
您編寫的構造函數必須調用超類構造函數(如往常一樣)。讓我們暫時不考慮該構造函數的參數。運行該程序的結果與前一個程序沒有任何不同。缺省情況下,程序不會為我們顯示任何裝飾性的東西。我們的程序要創建一個帶有文本“Hello, World”的按鈕。這個按鈕要顯示在內容(Contents)區域。要做到這一點,我們必須實現 Control createContents(Composite parent) 方法。
ApplicationWindow 將在所有其它窗口構件已經創建之后但窗口在屏幕上顯示之前調用該方法。參數 parent 是代表內容區域的復合窗口構件。這里的想法是您創建一個復合窗口構件,將其添加到 parent,然后添加您的窗口構件,并返回您創建的復合窗口構件。圖 5 演示了實例層次結構。
圖 5. Application Window 的實例層次結構 我們的內容目前非常簡單: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();
}
}
結果是圖 6
圖 6. Hello(版本 3) 這就是我們要實現的。我們使用 JFace 創建的第一個“Hello, World”程序:包含單一按鈕的窗口。現在讓我們繼續討論文件資源管理器這一話題。首先,我們將創建顯示文件夾層次結構的樹查看器。使用 TreeViewer 和 ApplicationWindow 一樣,TreeViewer 不是真正的 SWT 窗口構件,它也沒有打算向您隱藏 SWT 窗口構件。它使用 SWT 樹窗口構件來顯示各項,并且還使用許多其它對象來協助它。不象 ApplicationWindow,JFace TreeViewer 并不旨在被子類化。
這里的想法是 TreeViewer 知道要顯示的樹的根元素。當然,您必須告訴它那個對象是什么:TreeViewer: void setInput(Object rootElement)
為了開始顯示,樹查看器向根元素請求子元素并顯示它們。然后,當用戶展開其中的一個子元素時,樹查看器向該節點請求子元素,以此類推。實際上,并不完全是那樣。TreeViewer 并不直接使用域對象 — 而是使用另一個名為 ContentProvider 的對象,這個對象才使用域對象,如圖 7 所示。
圖 7. TreeViewer、ContentProvider 和域對象 當然,您必須實現 ContentProvider。對于 TreeViewer,您的類必須實現 ITreeContentProvider 接口。實現 TreeContentProvider
有六個方法需要實現。實際上我們不用做全部的工作,只需實現其中的三個就行,因此,本著“即時滿意(instant gratification)”的精神,讓我們暫時只考慮那幾個方法吧。下面的代碼演示了樹查看器如何向內容提供程序請求正好位于根元素下的頂級元素:
ITreeContentProvider: public Object[] getElements(Object element)
隨后,每當它需要特定元素的子元素時,它就使用以下方法:
ITreeContentProvider: public Object[] getChildren(Object element)
為了知道某個節點是否有子元素(有的話會將小加號放到它旁邊),樹查看器只需請求該節點的子元素,然后會詢問有多少子元素。萬一您的代碼需要更快捷的方法來做到這一點,則您必須實現另一個方法:
public boolean hasChildren(Object element)
正如您所見,內容提供程序不持有對任何域對象的引用。持有對這些域對象的引用的是樹查看器本身,它把這些域對象作為參數傳遞給內容提供程序中的各個方法。在我們的例子中,節點是 File 對象。為獲取子元素,我們使用 listFiles()。我們必須記得要檢查 listFiles() 是否返回 null,然后使其變成空數組。為了獲取頂級元素(正好位于根元素之下),我們只需重用 getChildren() 方法。
getParent() 方法被用來實現 reveal(Object element) 方法,后者使樹查看器滾動其 SWT 樹窗口構件,以便顯示樹中特定的節點。問題是:如果此刻實際上并沒有顯示那個節點,那么應該在哪里顯示它?JFace 會尋找其父元素,以及父元素的父元素等等,直到它達到已顯示的節點,然后它再次回頭尋找,直到目標節點已顯示。
hasChildren() 方法只是做了顯而易見(未優化)的事情,最后我們有了清單 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