開放源碼 Eclipse 項目是 Java 領域中最有趣的新近開發項目之一。Eclipse 把自己描述成“一種通用的工具平臺 — 開放的可擴展 IDE,可用于任何用途且沒有特殊之處”。關于 Eclipse 的介紹,請參閱
developerWorks文章“
Getting started with the Eclipse Platform”。
它的兩個主要組件是名為 SWT 的圖形庫和與其匹配的名為 JFace 的實用程序框架。在本文中,我將集中討論這些組件。Eclipse 網站(請參閱本文后面的
參考資料)中的 Eclipse 技術概述(Eclipse Technical Overview)對這兩個組件的描述如下:
-
SWT是一個窗口構件集和圖形庫,它集成于本機窗口系統但有獨立于 OS 的 API。
-
JFace是用 SWT 實現的 UI 工具箱,它簡化了常見的 UI 編程任務。JFace 在其 API 和實現方面都是獨立于窗口系統的,它旨在使用 SWT 而不隱藏它。
圖 1 演示了 Eclipse、JFace 和 SWT 之間的關系。
圖 1. Eclipse Workbench、JFace 和 SWT
已發表的關于 JFace 和 SWT 的大多數文章(到目前為止)就如何在較大型 Eclipse 框架的環境中使用它們進行了討論。在本文中,我打算采取另一種方法。我將向您演示如何在獨立的 Java 程序中使用 JFace 和 SWT。
我選擇的示例是文件資源管理器。我們實際上不會實現很多真正的功能,但我們將使用足夠多的 GUI,以使您了解如何構建一個功能完整的程序。
安裝說明
您可以下載本文中
示例的源代碼,但要把我的系統設置考慮在內:
- Windows 2000
- Eclipse,穩定構建 M3(2002 年 11 月 15 日)
- Eclipse 安裝在 C:\eclipse-2.1.0 中
隨后的所有調整名稱和文件分隔符的工作留給您完成,以便程序能在您的系統上正確地運行。
構建/運行指導
您需要將以下 jar 文件置于類路徑(class path)上:
C:\eclipse-2.1.0\plugins\org.eclipse.jface_2.1.0\jface.jar
C:\eclipse-2.1.0\plugins\org.eclipse.runtime_2.1.0\runtime.jar
C:\eclipse-2.1.0\plugins\org.eclipse.swt.win32_2.1.0\ws\win32\swt.jar
C:\eclipse-2.1.0\plugins\org.eclipse.ui.workbench_2.1.0\workbench.jar
C:\eclipse-2.1.0\plugins\org.eclipse.core.runtime_2.1.0\runtime.jar
為確保 Java VM 獲得您在運行時使用的 GUI 的正確共享庫,請結合以下參數運行它:
-Djava.library.path=C:\eclipse-2.1.0\plugins\org.eclipse.swt.win32_2.1.0\os\win32\x86\
最后,請在包含
icons文件夾的文件夾中運行程序,這樣示例就可以找到包含圖標的 gif 文件。
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) { Object[] kids = ((File) element).listFiles(); return kids == null ? new Object[0] : kids; } public Object[] getElements(Object element) { return getChildren(element); } public boolean hasChildren(Object element) { return getChildren(element).length > 0; } public Object getParent(Object element) { return ((File)element).getParent(); } public void dispose() { } public void inputChanged(Viewer viewer, Object old_input, Object new_input) { } }
|
實現頂級 Explorer 類
我們將采用 Hello, World 程序,更改其名稱,然后用
createContents()
方法創建
TreeViewer
(而不是創建一個按鈕),將其內容提供程序設置為我們的文件樹內容提供程序,然后將輸入設置到某個文件夾。在這個例子中,我選擇的文件夾是 C: 驅動器中的頂級文件夾。
注:需要從
createContents()
返回 SWT 窗口構件。正如前面提到的,JFace Tree Viewer 不是 SWT 窗口構件,因此我們不能將它返回。我們需要從樹查看器獲取真正的窗口構件。我們通過使用
getTree()
做到這一點。
我們的主窗口類現在看起來與下面相似:
清單 5. Explorer(版本 1)
import java.io.*; import org.eclipse.jface.viewers.*; import org.eclipse.jface.window.*; import org.eclipse.swt.*; import org.eclipse.swt.widgets.*; public class Explorer extends ApplicationWindow { public Explorer() { super(null); } protected Control createContents(Composite parent) { TreeViewer tv = new TreeViewer(parent); tv.setContentProvider(new FileTreeContentProvider()); tv.setInput(new File("C:\\")); return tv.getTree(); } public static void main(String[] args) { Explorer w = new Explorer(); w.setBlockOnOpen(true); w.open(); Display.getCurrent().dispose(); } }
|
運行該程序,您將看到與圖 8 相似的結果。
圖 8. Explorer(版本 1)
除了樣板文件代碼,我們只需向 Hello, World 程序添加 9 行代碼就可做到這一點。
正如您可能猜想的那樣,程序用
File 的
toString()
方法來顯示這些文件,這不是我們真正想要的。要改變這一點,我們需要提供一個標簽提供程序。
實現標簽提供程序
正如有一個內容提供程序對象可用來獲取樹節點的子元素一樣,當需要實際顯示這些節點時,樹查看器有另一個助手對象:標簽提供程序。和前面一樣,我們需要設置它:
public void setLabelProvider(IBaseLabelProvider labelProvider)
而且需要實現下面的方法以返回要為每個元素顯示的文本:
public String getText(Object element)
如果我們將標簽提供程序添加到樹查看器圖中,就會得到圖 9。
圖 9. 顯示內容提供程序和標簽提供程序的樹查看器
我們可以實現接口
ILabelProvider
,但將缺省實現
LabelProvider
子類化更容易(如果沒有顯式地設置標簽提供程序,則使用的就是這個類)。
我們希望利用
getText()
做的事是返回文件名最后的部分 — 相對文件名而非
toString()
缺省使用的絕對文件名。圖 6 演示了代碼。
圖 6. FileTreeLabelProvider(版本 1)
import java.io.*; import org.eclipse.jface.viewers.*; public class FileTreeLabelProvider extends LabelProvider { public String getText(Object element) { return ((File) element).getName(); } }
|
而且我們必須記得使樹查看器使用這個標簽提供程序,如清單 7 所示。
清單 7. Explorer(版本 2)
import java.io.*; import org.eclipse.jface.viewers.*; import org.eclipse.jface.window.*; import org.eclipse.swt.*; import org.eclipse.swt.widgets.*; public class Explorer extends ApplicationWindow { public Explorer() { super(null); } protected Control createContents(Composite parent) { TreeViewer tv = new TreeViewer(parent); tv.setContentProvider(new FileTreeContentProvider()); tv.setLabelProvider(new FileTreeLabelProvider()); tv.setInput(new File("C:\\")); return tv.getTree(); } public static void main(String[] args) { Explorer w = new Explorer(); w.setBlockOnOpen(true); w.open(); Display.getCurrent().dispose(); } }
|
這一次運行該程序時,我們會獲得更清楚的視覺效果,如圖 10 所示。
圖 10. Explorer(版本 2)
我們現在要做的是將樹查看器移到左邊,將一個表查看器放在右邊,以顯示在樹查看器中已選中的文件夾中的文件列表。
使用表查看器
為了處理表,JFace 有一個
TableViewer
。和
TreeViewer
一樣,它有輸入(根對象)、內容提供程序和標簽提供程序。它比樹查看器簡單,因為它不需要處理樹。圖 11 演示了內容提供程序和標簽提供程序。
圖 11. 顯示內容提供程序和標簽提供程序的表查看器
設置輸入對象的方法與前面相同:
TableViewer: void setInput(Object rootElement)
實現文件表查看器內容提供程序
讓我們考慮內容提供程序。這一次,根元素比樹查看器根元素簡單。表查看器僅僅期望根對象有許多子元素,因此要實現的唯一有趣方法是獲取子元素的方法:
public Object[] getElements(Object rootElement)
要實現的接口是
IStructuredContentProvider
。
根對象是一個文件夾;其子元素是該文件夾包含的文件/文件夾。因此我們的文件表內容提供程序類與清單 8 類似。
清單 8. FileTableContentProvider(版本 1)
import java.io.*; import org.eclipse.jface.viewers.*; public class FileTableContentProvider implements IStructuredContentProvider { public Object[] getElements(Object element) { Object[] kids = null; kids = ((File) element).listFiles(); return kids == null ? new Object[0] : kids; } public void dispose() { } public void inputChanged(Viewer viewer, Object old_object, Object new_object) { } }
|
因此我們現在有兩個查看器:樹查看器和表查看器。為了將它們相鄰地安置在一起,我們創建了 SWT SashForm 窗口構件。該窗口構件用一個用戶可以調節的邊框分隔其子元素。然后,我們將樹和表添加到框格表單(sash form)(圖 12)。
圖 12. 包含樹查看器和表查看器的框格表單
接下來要做的是使表查看器查看用戶在樹查看器中選中的每個文件夾。要做到這一點,我們必須偵聽事件。
偵聽事件
當用戶在樹查看器中選中一項時,樹查看器發出
SelectionChangedEvent
事件。我們需要偵聽該事件,當發出該事件時,需要將表的輸入設置為樹查看器中當前選中的文件。
為了偵聽來自樹查看器的選擇更改事件,我們使用下面的方法:
public void addSelectionChangedListener(ISelectionChangedListener listener)
當用戶選中/取消選中樹查看器中的節點時,用下面的方法調用選擇更改偵聽器:
public void selectionChanged(SelectionChangedEvent event)
為了實現該偵聽器類,我們將在主資源管理器窗口中編碼一個匿名類。在
selectionChanged()
方法中,我們將需要獲得剛選中的對象,并使其成為表查看器的輸入。將所有的工作組合在一起,就得到了清單 9。
清單 9. Explorer(版本 3)
import java.io.*; import org.eclipse.jface.viewers.*; import org.eclipse.jface.window.*; import org.eclipse.swt.*; import org.eclipse.swt.custom.*; import org.eclipse.swt.widgets.*; public class Explorer extends ApplicationWindow { public Explorer() { super(null); } protected Control createContents(Composite parent) { SashForm sash_form = new SashForm(parent, SWT.HORIZONTAL | SWT.NULL); TreeViewer tv = new TreeViewer(sash_form); tv.setContentProvider(new FileTreeContentProvider()); tv.setLabelProvider(new FileTreeLabelProvider()); tv.setInput(new File("C:\\")); final TableViewer tbv = new TableViewer(sash_form, SWT.BORDER); tbv.setContentProvider(new FileTableContentProvider()); tv.addSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { IStructuredSelection selection = (IStructuredSelection) event.getSelection(); Object selected_file = selection.getFirstElement(); tbv.setInput(selected_file); } }); return sash_form; } public static void main(String[] args) { Explorer w = new Explorer(); w.setBlockOnOpen(true); w.open(); Display.getCurrent().dispose(); } }
|
如果運行該程序,就會得到類似圖 13 的結果。
圖 13. Explorer(版本 3)
和樹查看器一樣,如果不顯式地給表查看器設置標簽提供程序,它就會使用缺省的標簽提供程序。這就是這里發生的情況 — 如果您還記得的話,缺省行為是顯示由元素的
toString()
方法返回的字符串,它正好是絕對文件名。
讓我們實現自己的表標簽提供程序。
實現文件表標簽提供程序
現在只需考慮一個方法:
public String getColumnText(Object element, int column)
這里有兩個參數:要獲取其標簽的元素和列索引(從 0 開始)。
該方法的實現相當簡單 — 如果我們不考慮列索引參數的話,如清單 10 所示。
清單 10. FileTableLabelProvider(版本 1)
import java.io.*; import org.eclipse.jface.viewers.*; import org.eclipse.swt.graphics.*; public class FileTableLabelProvider implements ITableLabelProvider { public String getColumnText(Object obj, int i) { return ((File) obj).getName(); } public void addListener(ILabelProviderListener ilabelproviderlistener) { } public void dispose() { } public boolean isLabelProperty(Object obj, String s) { return false; } public void removeListener(ILabelProviderListener ilabelproviderlistener) { } public Image getColumnImage(Object arg0, int arg1) { return null; } }
|
要配置表使其包含一列且列首標簽為“Name”,必須從表查看器抽取表窗口構件,將表的列這一窗口構件作為表的子元素來創建,并給它設置一些屬性,如清單 11 中所示。
清單 11. Explorer(版本 4)
import java.io.*; import org.eclipse.jface.viewers.*; import org.eclipse.jface.window.*; import org.eclipse.swt.*; import org.eclipse.swt.custom.*; import org.eclipse.swt.widgets.*; public class Explorer extends ApplicationWindow { public Explorer() { super(null); } protected Control createContents(Composite parent) { SashForm sash_form = new SashForm(parent, SWT.HORIZONTAL | SWT.NULL); TreeViewer tv = new TreeViewer(sash_form); tv.setContentProvider(new FileTreeContentProvider()); tv.setLabelProvider(new FileTreeLabelProvider()); tv.setInput(new File("C:\\")); final TableViewer tbv = new TableViewer(sash_form, SWT.BORDER); tbv.setContentProvider(new FileTableContentProvider()); tbv.setLabelProvider(new FileTableLabelProvider()); TableColumn column = new TableColumn(tbv.getTable(), SWT.LEFT); column.setText("Name"); column.setWidth(200); tbv.getTable().setHeaderVisible(true); tv.addSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { IStructuredSelection selection = (IStructuredSelection) event.getSelection(); Object selected_file = selection.getFirstElement(); tbv.setInput(selected_file); } }); return sash_form; } public static void main(String[] args) { Explorer w = new Explorer(); w.setBlockOnOpen(true); w.open(); Display.getCurrent().dispose(); }
開放源碼 Eclipse 項目是 Java 領域中最有趣的新近開發項目之一。Eclipse 把自己描述成“一種通用的工具平臺 — 開放的可擴展 IDE,可用于任何用途且沒有特殊之處”。關于 Eclipse 的介紹,請參閱
developerWorks文章“
Getting started with the Eclipse Platform”。
它的兩個主要組件是名為 SWT 的圖形庫和與其匹配的名為 JFace 的實用程序框架。在本文中,我將集中討論這些組件。Eclipse 網站(請參閱本文后面的
參考資料)中的 Eclipse 技術概述(Eclipse Technical Overview)對這兩個組件的描述如下:
-
SWT是一個窗口構件集和圖形庫,它集成于本機窗口系統但有獨立于 OS 的 API。
-
JFace是用 SWT 實現的 UI 工具箱,它簡化了常見的 UI 編程任務。JFace 在其 API 和實現方面都是獨立于窗口系統的,它旨在使用 SWT 而不隱藏它。
圖 1 演示了 Eclipse、JFace 和 SWT 之間的關系。
圖 1. Eclipse Workbench、JFace 和 SWT
已發表的關于 JFace 和 SWT 的大多數文章(到目前為止)就如何在較大型 Eclipse 框架的環境中使用它們進行了討論。在本文中,我打算采取另一種方法。我將向您演示如何在獨立的 Java 程序中使用 JFace 和 SWT。
我選擇的示例是文件資源管理器。我們實際上不會實現很多真正的功能,但我們將使用足夠多的 GUI,以使您了解如何構建一個功能完整的程序。
安裝說明
您可以下載本文中
示例的源代碼,但要把我的系統設置考慮在內:
- Windows 2000
- Eclipse,穩定構建 M3(2002 年 11 月 15 日)
- Eclipse 安裝在 C:\eclipse-2.1.0 中
隨后的所有調整名稱和文件分隔符的工作留給您完成,以便程序能在您的系統上正確地運行。
構建/運行指導
您需要將以下 jar 文件置于類路徑(class path)上:
C:\eclipse-2.1.0\plugins\org.eclipse.jface_2.1.0\jface.jar
C:\eclipse-2.1.0\plugins\org.eclipse.runtime_2.1.0\runtime.jar
C:\eclipse-2.1.0\plugins\org.eclipse.swt.win32_2.1.0\ws\win32\swt.jar
C:\eclipse-2.1.0\plugins\org.eclipse.ui.workbench_2.1.0\workbench.jar
C:\eclipse-2.1.0\plugins\org.eclipse.core.runtime_2.1.0\runtime.jar
為確保 Java VM 獲得您在運行時使用的 GUI 的正確共享庫,請結合以下參數運行它:
-Djava.library.path=C:\eclipse-2.1.0\plugins\org.eclipse.swt.win32_2.1.0\os\win32\x86\
最后,請在包含
icons文件夾的文件夾中運行程序,這樣示例就可以找到包含圖標的 gif 文件。
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) { Object[] kids = ((File) element).listFiles(); return kids == null ? new Object[0] : kids; } public Object[] getElements(Object element) { return getChildren(element); } public boolean hasChildren(Object element) { return getChildren(element).length > 0; } public Object getParent(Object element) { return ((File)element).getParent(); } public void dispose() { } public void inputChanged(Viewer viewer, Object old_input, Object new_input) { } }
|
實現頂級 Explorer 類
我們將采用 Hello, World 程序,更改其名稱,然后用
createContents() 方法創建
TreeViewer (而不是創建一個按鈕),將其內容提供程序設置為我們的文件樹內容提供程序,然后將輸入設置到某個文件夾。在這個例子中,我選擇的文件夾是 C: 驅動器中的頂級文件夾。
注:需要從
createContents() 返回 SWT 窗口構件。正如前面提到的,JFace Tree Viewer 不是 SWT 窗口構件,因此我們不能將它返回。我們需要從樹查看器獲取真正的窗口構件。我們通過使用
getTree() 做到這一點。
我們的主窗口類現在看起來與下面相似:
清單 5. Explorer(版本 1)
import java.io.*; import org.eclipse.jface.viewers.*; import org.eclipse.jface.window.*; import org.eclipse.swt.*; import org.eclipse.swt.widgets.*; public class Explorer extends ApplicationWindow { public Explorer() { super(null); } protected Control createContents(Composite parent) { TreeViewer tv = new TreeViewer(parent); tv.setContentProvider(new FileTreeContentProvider()); tv.setInput(new File("C:\\")); return tv.getTree(); } public static void main(String[] args) { Explorer w = new Explorer(); w.setBlockOnOpen(true); w.open(); Display.getCurrent().dispose(); } }
|
運行該程序,您將看到與圖 8 相似的結果。
圖 8. Explorer(版本 1)
除了樣板文件代碼,我們只需向 Hello, World 程序添加 9 行代碼就可做到這一點。
正如您可能猜想的那樣,程序用
File 的
toString() 方法來顯示這些文件,這不是我們真正想要的。要改變這一點,我們需要提供一個標簽提供程序。
實現標簽提供程序
正如有一個內容提供程序對象可用來獲取樹節點的子元素一樣,當需要實際顯示這些節點時,樹查看器有另一個助手對象:標簽提供程序。和前面一樣,我們需要設置它:
public void setLabelProvider(IBaseLabelProvider labelProvider)
而且需要實現下面的方法以返回要為每個元素顯示的文本:
public String getText(Object element)
如果我們將標簽提供程序添加到樹查看器圖中,就會得到圖 9。
圖 9. 顯示內容提供程序和標簽提供程序的樹查看器
我們可以實現接口
ILabelProvider ,但將缺省實現
LabelProvider 子類化更容易(如果沒有顯式地設置標簽提供程序,則使用的就是這個類)。
我們希望利用
getText() 做的事是返回文件名最后的部分 — 相對文件名而非
toString() 缺省使用的絕對文件名。圖 6 演示了代碼。
圖 6. FileTreeLabelProvider(版本 1)
import java.io.*; import org.eclipse.jface.viewers.*; public class FileTreeLabelProvider extends LabelProvider { public String getText(Object element) { return ((File) element).getName(); } }
|
而且我們必須記得使樹查看器使用這個標簽提供程序,如清單 7 所示。
清單 7. Explorer(版本 2)
import java.io.*; import org.eclipse.jface.viewers.*; import org.eclipse.jface.window.*; import org.eclipse.swt.*; import org.eclipse.swt.widgets.*; public class Explorer extends ApplicationWindow { public Explorer() { super(null); } protected Control createContents(Composite parent) { TreeViewer tv = new TreeViewer(parent); tv.setContentProvider(new FileTreeContentProvider()); tv.setLabelProvider(new FileTreeLabelProvider()); tv.setInput(new File("C:\\")); return tv.getTree(); } public static void main(String[] args) { Explorer w = new Explorer(); w.setBlockOnOpen(true); w.open(); Display.getCurrent().dispose(); } }
|
這一次運行該程序時,我們會獲得更清楚的視覺效果,如圖 10 所示。
圖 10. Explorer(版本 2)
我們現在要做的是將樹查看器移到左邊,將一個表查看器放在右邊,以顯示在樹查看器中已選中的文件夾中的文件列表。
使用表查看器
為了處理表,JFace 有一個
TableViewer 。和
TreeViewer 一樣,它有輸入(根對象)、內容提供程序和標簽提供程序。它比樹查看器簡單,因為它不需要處理樹。圖 11 演示了內容提供程序和標簽提供程序。
圖 11. 顯示內容提供程序和標簽提供程序的表查看器
設置輸入對象的方法與前面相同:
TableViewer: void setInput(Object rootElement)
實現文件表查看器內容提供程序
讓我們考慮內容提供程序。這一次,根元素比樹查看器根元素簡單。表查看器僅僅期望根對象有許多子元素,因此要實現的唯一有趣方法是獲取子元素的方法:
public Object[] getElements(Object rootElement)
要實現的接口是
IStructuredContentProvider 。
根對象是一個文件夾;其子元素是該文件夾包含的文件/文件夾。因此我們的文件表內容提供程序類與清單 8 類似。
清單 8. FileTableContentProvider(版本 1)
import java.io.*; import org.eclipse.jface.viewers.*; public class FileTableContentProvider implements IStructuredContentProvider { public Object[] getElements(Object element) { Object[] kids = null; kids = ((File) element).listFiles(); return kids == null ? new Object[0] : kids; } public void dispose() { } public void inputChanged(Viewer viewer, Object old_object, Object new_object) { } }
|
因此我們現在有兩個查看器:樹查看器和表查看器。為了將它們相鄰地安置在一起,我們創建了 SWT SashForm 窗口構件。該窗口構件用一個用戶可以調節的邊框分隔其子元素。然后,我們將樹和表添加到框格表單(sash form)(圖 12)。
圖 12. 包含樹查看器和表查看器的框格表單
接下來要做的是使表查看器查看用戶在樹查看器中選中的每個文件夾。要做到這一點,我們必須偵聽事件。
偵聽事件
當用戶在樹查看器中選中一項時,樹查看器發出
SelectionChangedEvent 事件。我們需要偵聽該事件,當發出該事件時,需要將表的輸入設置為樹查看器中當前選中的文件。
為了偵聽來自樹查看器的選擇更改事件,我們使用下面的方法:
public void addSelectionChangedListener(ISelectionChangedListener listener)
當用戶選中/取消選中樹查看器中的節點時,用下面的方法調用選擇更改偵聽器:
public void selectionChanged(SelectionChangedEvent event)
為了實現該偵聽器類,我們將在主資源管理器窗口中編碼一個匿名類。在
selectionChanged() 方法中,我們將需要獲得剛選中的對象,并使其成為表查看器的輸入。將所有的工作組合在一起,就得到了清單 9。
清單 9. Explorer(版本 3)
import java.io.*; import org.eclipse.jface.viewers.*; import org.eclipse.jface.window.*; import org.eclipse.swt.*; import org.eclipse.swt.custom.*; import org.eclipse.swt.widgets.*; public class Explorer extends ApplicationWindow { public Explorer() { super(null); } protected Control createContents(Composite parent) { SashForm sash_form = new SashForm(parent, SWT.HORIZONTAL | SWT.NULL); TreeViewer tv = new TreeViewer(sash_form); tv.setContentProvider(new FileTreeContentProvider()); tv.setLabelProvider(new FileTreeLabelProvider()); tv.setInput(new File("C:\\")); final TableViewer tbv = new TableViewer(sash_form, SWT.BORDER); tbv.setContentProvider(new FileTableContentProvider()); tv.addSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { IStructuredSelection selection = (IStructuredSelection) event.getSelection(); Object selected_file = selection.getFirstElement(); tbv.setInput(selected_file); } }); return sash_form; } public static void main(String[] args) { Explorer w = new Explorer(); w.setBlockOnOpen(true); w.open(); Display.getCurrent().dispose(); } }
|
如果運行該程序,就會得到類似圖 13 的結果。
圖 13. Explorer(版本 3)
和樹查看器一樣,如果不顯式地給表查看器設置標簽提供程序,它就會使用缺省的標簽提供程序。這就是這里發生的情況 — 如果您還記得的話,缺省行為是顯示由元素的
toString() 方法返回的字符串,它正好是絕對文件名。
讓我們實現自己的表標簽提供程序。
實現文件表標簽提供程序
現在只需考慮一個方法:
public String getColumnText(Object element, int column)
這里有兩個參數:要獲取其標簽的元素和列索引(從 0 開始)。
該方法的實現相當簡單 — 如果我們不考慮列索引參數的話,如清單 10 所示。
清單 10. FileTableLabelProvider(版本 1)
import java.io.*; import org.eclipse.jface.viewers.*; import org.eclipse.swt.graphics.*; public class FileTableLabelProvider implements ITableLabelProvider { public String getColumnText(Object obj, int i) { return ((File) obj).getName(); } public void addListener(ILabelProviderListener ilabelproviderlistener) { } public void dispose() { } public boolean isLabelProperty(Object obj, String s) { return false; } public void removeListener(ILabelProviderListener ilabelproviderlistener) { } public Image getColumnImage(Object arg0, int arg1) { return null; } }
|
要配置表使其包含一列且列首標簽為“Name”,必須從表查看器抽取表窗口構件,將表的列這一窗口構件作為表的子元素來創建,并給它設置一些屬性,如清單 11 中所示。
清單 11. Explorer(版本 4)
import java.io.*; import org.eclipse.jface.viewers.*; import org.eclipse.jface.window.*; import org.eclipse.swt.*; import org.eclipse.swt.custom.*; import org.eclipse.swt.widgets.*; public class Explorer extends ApplicationWindow { public Explorer() { super(null); } protected Control createContents(Composite parent) { SashForm sash_form = new SashForm(parent, SWT.HORIZONTAL | SWT.NULL); TreeViewer tv = new TreeViewer(sash_form); tv.setContentProvider(new FileTreeContentProvider()); tv.setLabelProvider(new FileTreeLabelProvider()); tv.setInput(new File("C:\\")); final TableViewer tbv = new TableViewer(sash_form, SWT.BORDER); tbv.setContentProvider(new FileTableContentProvider()); tbv.setLabelProvider(new FileTableLabelProvider()); TableColumn column = new TableColumn(tbv.getTable(), SWT.LEFT); column.setText("Name"); column.setWidth(200); tbv.getTable().setHeaderVisible(true); tv.addSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { IStructuredSelection selection = (IStructuredSelection) event.getSelection(); Object selected_file = selection.getFirstElement(); tbv.setInput(selected_file); } }); return sash_form; } public static void main(String[] args) { Explorer w = new Explorer(); w.setBlockOnOpen(true); w.open(); Display.getCurrent().dispose(); } }
|
運行該程序,我們應得到類似圖 14 的結果。
圖 14. Explorer(版本 4)
結束語
我們在很短的時間里完成了非常多的 JFace 編碼工作。我們已經使用了一個應用程序窗口和兩個查看器(樹和表),并實現了它們的內容和標簽提供程序。我們已經使用的 SWT 窗口構件有:Button、SashForm、Table 和 TableColumn,而且實現了一個事件偵聽器。
但本文也有一些不完善的地方。我們沒有考慮內容/標簽提供程序中的一些方法,樹查看器既顯示文件又顯示文件夾,沒有顯示圖標,而且沒有涉及菜單欄、工具欄、狀態欄或任何彈出菜單。
在下一篇文章中,我們將整理內容/標簽提供程序,并對查看器進行排序和過濾。我們將給窗口添加狀態欄,給兩個查看器都添加圖標,并了解 JFace 圖像注冊表(image registry)。
}
|
運行該程序,我們應得到類似圖 14 的結果。
圖 14. Explorer(版本 4)
結束語
我們在很短的時間里完成了非常多的 JFace 編碼工作。我們已經使用了一個應用程序窗口和兩個查看器(樹和表),并實現了它們的內容和標簽提供程序。我們已經使用的 SWT 窗口構件有:Button、SashForm、Table 和 TableColumn,而且實現了一個事件偵聽器。
但本文也有一些不完善的地方。我們沒有考慮內容/標簽提供程序中的一些方法,樹查看器既顯示文件又顯示文件夾,沒有顯示圖標,而且沒有涉及菜單欄、工具欄、狀態欄或任何彈出菜單。
在下一篇文章中,我們將整理內容/標簽提供程序,并對查看器進行排序和過濾。我們將給窗口添加狀態欄,給兩個查看器都添加圖標,并了解 JFace 圖像注冊表(image registry)。
來源:http://www-128.ibm.com/developerworks/cn/linux/opensource/os-ecgui1/index.html