Eclipse RCP詳解(05):JFace和結構化數據
Posted on 2014-03-04 20:56 京山游俠 閱讀(5261) 評論(6) 編輯 收藏 所屬分類: 擁抱Eclipse RCP上一篇
Eclipse RCP詳解(04):Eclipse RCP相關的學習資料及國內相關圖書點評
前面幾篇都是使用Ubuntu系統及其自帶的Eclipse 3.8.1。在這一篇里,我將為大家展示Fedora 20和其自帶的Eclipse 4.3.1。同時,為了方便我隨時切換操作系統(Ubuntu、Fedora、Win7),我在GitHub上注冊了一個賬號,并把示例代碼放了上去。我切換系統后,只需要Fetch一下,就可以繼續開發。當然,有興趣看我代碼的朋友,也可以clone一份到自己的機器上。地址:
https://github.com/littleloach/examples.git
關于Eclipse和Git的整合,請看這里:淺論Maven和Git的原理及展示其與Eclipse的集成
事實證明,Eclipse RCP不僅是跨平臺的,而且是跨版本的。也就是說,即使我在Fedora中使用了不一樣的Eclipse版本,我在Ubuntu中寫的代碼到Fedora中也不需要修改。(我在Win7中用的Eclipse版本和Ubuntu中的相同。)
Fedora 20和Fedora 19都是我喜歡的版本。因為它們的開發代號分別為Heisenberg和薛定諤的貓,不難看出,它們都和量子力學有關,對我這樣的理工男吸引力那肯定是巨大的。而Heisenberg更具有傳奇性。不僅因為他是量子力學的主要創始人和諾貝爾獎獲得者,更因為歷史上到現在都沒有定論的海森堡謎題:為什么當初他沒有造出原子彈,是他真的搞錯了,還是他故意搞錯了?故事是這樣的:第二次世界大戰開始后,迫于納粹德國的威脅,丹麥的大物理學家玻爾離開了心愛的哥本哈根理論物理研究所,離開了朝夕相處的來自世界各地的同事,遠赴美國。德國的許多科學家也紛紛背井離鄉,堅決不與納粹勢力妥協。然而,有一位同樣優秀的物理學家卻留下來了,并被納粹德國委以重任,負責領導研制原子彈的技術工作,遠在異鄉的玻爾憤怒了,他與這位過去的同事產生了尖銳的矛盾,并與他形成了終生未能化解的隔閡。有趣的是,這位一直未能被玻爾諒解的科學家卻在1970年獲得了“玻爾國際獎章”,而這一獎章是用以表彰“在原子能和平利用方面做出了巨大貢獻的科學家或工程師”的。歷史在此開了個巨大的玩笑,這玩笑的主人公就像他發現的“測不準原理”一樣,一直讓人感到困惑和不解。他就是量子力學的創始人——海森堡。也許他當初就是故意留下來而且故意造不出原子彈的吧。
在正式介紹JFace和結構化數據之前,先向大家展示幾張Fedora的截圖。
第一張,Fedora剛安裝完成后,其Gnome桌面是很丑的:

所以需要進行一些Art Work,比如多安裝幾個wallpaper啦,多安裝幾個theme啦,然后安裝一個gnome-tweak-tool更改一下字體、字體微調和抗鋸齒啦。Fedora 19和20還有一個地方比較令人蛋疼,那就是它提供的gnome-terminal是不支持半透明背景的,如果想要半透明背景,只有自己編譯gnome-terminal。我太懶,所以只好把KDE中的konsole借來用用了。好在KDE程序在gnome中也可以正常運行。下面是經過我折騰后的Fedora 20:

有人說,GTK+程序的列表兩行之間的寬度大到可以塞進一頭大象。以前在Ubuntu下用Eclipse不覺得,換到Fedora中發現這個寬度確實是挺大的,看著很不舒服。不過這都不是個事,換個GTK+的theme一切都搞定。下面一張是Eclipse的截圖,還是很漂亮的說:

下面開始今天的正題。
結構化數據
結構化數據可不是我一拍腦袋想出來的術語。平時我們在組織和保存數據的時候,離不開數據結構。我們經常使用的數組、鏈表或多維數組來保存數據,也經常使用數據庫的表格保存數據,但是我們并不是時時刻刻想著結構化這么一個概念。但是在Eclipse中就不一樣了,結構化數據是它的一個很重要的理念,只要有可能,它都會把數據組織成結構化的。比如,如果你在Eclipse中選擇了一些什么,那么這些選擇的東西就是以IStructuredSelection展示出來的。如下圖,可見Structured無處不在:

數組(包括鏈表)、樹和表格(包括多維數組)是我們常用的數據結構。List控件、Tree控件和Table控件是我們常用的展示數據的控件。然而邏輯和形式并不總是一一對應的。比如形式上可以用Tree控件表現的數據,邏輯上并不一定是用樹結構組織起來的數據。想想我們做數據庫的ORM映射,如果一個表中的一行數據和另一個表中的多行數據相關聯(一對多映射),寫成Java Entity類的時候就是一個Entity中包含有一個另一個Entity的List或Set。這兩個Entity各有各的Class Name,而不是統一叫TreeNode。這兩個Entity之間也不用getParent和getChildren互相訪問。但是從形式上,依然可以用一個Tree控件來展示它們,不是嗎?
JFace中的Viewer
在該系列博文的第02篇中,我展示了SWT的List控件和Tree控件。這兩個控件的使用是很簡單的,List控件只需要使用add()方法就可以在列表中添加一個字符串,Tree控件需要先構造一個TreeItem對象,然后用setText()方法添加一個字符串??丶荒苡脕盹@示字符串和字符串前面的一個小圖標,不能保存復雜的數據。MVC是常用的設計模式,Model用來保存數據,而控件只是負責顯示,所以中間一定要有一個機制將Model中的數據轉換為能讓控件顯示的字符串和圖標,并且要能維持數據在結構化上的邏輯聯系。
JFace中的Viewer是對SWT中的控件的封裝,它使用的正是MVC模式。如果不了解Viewer的哲學,使用Viewer就無從下手。Viewer的哲學是這樣的:Viewer對它要顯示的數據是怎么組織的沒有任何要求,可以使用它的setInput()方法將任何一個對象作為它的Model;很顯然Viewer不可能智能到顯示任何結構的數據,所以必須給它提供一個ContentProvider,ContentProvider就是負責將任意類型的Model轉化為結構化的數據;Model經過ContentProvider轉化為結構化數據后,Viewer再對該結構中的每一項進行顯示,前面提到過控件只能顯示文字和圖片,所以還需要一個LabelProvider來將數據轉化成要顯示的文字和圖片。
由上面的描述可以看出,要使用Viewer必須最少編寫三樣東西:一些Model、一個ContentProvider和一個LabelProvider。當然,Viewer還有更多的功能,比如對數據進行排序和過濾,只需要提供相應的SortProvider和FilterProvider即可,但這兩個Provider不是必須的。雖然要寫的類比較多,但是JFace都定義好了Interface,我們只需要實現即可,在IDE的幫助下,這個工作一點也不難。
一個簡單的示例
文字內容寫得再多,也不如一個例子來得直接。我這里模擬HIS(Hospital Information System)系統中一個簡單的功能來展示Viewer的用法。在醫院里,一個住院部(Inpatient Department)可以有很多個科室(Department),比如內科、外科、五官科等,一個科室里面可以有很多個病人(Patient)。很顯然,這些數據用一個TreeViewer顯示是再合適不過了。先看看這個示例程序界面的總體框架,如下圖:

這只是一個框架,結合上面的界面,我簡要說說我要實現的功能:
1、最左邊的視圖使用了一個TreeViewer,用來顯示住院部的所有科室和病人;
2、工具欄有兩個按鈕,如果最左邊的視圖中被選擇的對象是科室,則帶加號的按鈕可用,其功能是為該科室增加一個病人;如果最左邊的視圖中被選擇的對象是病人,則帶減號的按鈕可用,其功能是刪除這個病人;
3、中間的視圖使用一個TableViewer控件,當最左邊的視圖中被選擇的對象是科室時,則以表格的形式顯示該科室所有病人的詳細信息,每一行代表一個病人;
4、最右邊的視圖使用一個ListViewer控件,僅僅顯示當前選擇的內容。
為示例程序準備數據
該示例程序用到的數據是一個住院部有多個科室、一個科室有多個病人,很顯然這是一個典型的一對多的關系。所以我們的Model需要三個類:Inpatient、Department、Patient。其關系如下:

其中的Gender是一個枚舉。下面是它們的代碼:
Gender.java
Inpatient.java
Department.java
Patient.java
然后,創建一個PatientView類,在里面使用一個TreeViewer控件,創建一個Inpatient類的實例,然后添加一點點測試數據,然后把Inpatient的實例作為viewer的input即可。如下圖:

當然,如果沒有設置好ContentProvider和LabelProvider,數據是不可能正常顯示的。所以我們還需要創建一個PatientContentProvider類和PatientLabelProvider類。好在JFace中有早就定義好的接口,我們只需要從這些接口實現即可,如下圖:

我們先來看PatientContentProvider需要實現哪些方法,如下圖,可以看出IDE已經自動把框架搭建起來了,我們只需要填空即可(下圖中的空是我已經填好了的):

第一個要實現的方法是getElements,該方法要求我們把Input的數據轉化成一個Object數組返回。在這個例子中,Input進來的是Inpatient類的對象,所以只需要先將inputElement轉型為Inpatient,然后返回它里面的Department數組即可。第二個要實現的方法是hasChildren,在本例中,如果element是Department,則它就有children,如果是Patient就沒有children。最后要實現的一對方法就是getChildren和getParent,很顯然,如果element是Department,則它的children是它里面保存的List<Patient>,不過返回的時候要把它轉化成數組,如果element是Patient,則它的parent是它的department。So easy!不是嗎?
而PatientLabelProvider的實現就更簡單了,如下圖:
看圖填空,我們只需要完成getText方法和getImage方法即可。邏輯也很簡單,就是判斷element是Department還是Patient,然后分別返回相應的字符串和圖片即可。在這里,我還故意把不同的性別顯示為不同的圖標。有了截圖,我就不貼代碼了。
俗話說一通百通,使用不同的Model和Viewer,就給它不同的ContentProvider和LabelProvider即可。
處理Selection事件
關于數據的展示,我們輕松搞定。不過這還不算完,還有工作要做?,F在數據顯示出來了,我們就可以用鼠標來點擊,來選擇。那么我們選擇的數據怎么樣來影響我們程序的行為呢?在前面幾篇對GUI的探討中我說過,程序和用戶的交互是通過事件處理來進行的。也就是說,當我們選擇了Viewer中的一項或多項的時候,Viewer應該發送一個Selection事件,我們向程序中注冊一個SelectionListener就應該可以捕獲到這個事件。
為了讓patientViewer向程序發送Selection事件,我們需要讓它成為程序的selectionProvider。通過在PatientView中添加如下一行代碼(最后一行):

有了發送Selection事件的源,接著應該有接受Selection事件的Listener。先從最簡單的功能做起。最右邊的視圖只是用來顯示所選擇的對象,所以從它先開始。最右邊的視圖類是SelectionView,里面用了一個ListViewer,在這里,我們先讓SelectionView實現ISelectionListener接口,然后把它注冊到Workbench中。如下圖,關鍵代碼我把它標出來了:

selectionChanged()方法將在有目標被選中的時候調用,該方法的實現也很簡單,就是把selection作為ListViewer的input,讓它顯示即可。而要使用ListViewer,我們又得為它提供一個ContentProvider和一個LabelProvider。下面是ContentProvider和LabelProvider的實現,也很簡單,這次ContentProvider實現了IStructuredContentProvider接口,而且從代碼中可以看出,selection是一個IStructuredSelection,哪怕我們只選中一項,它也是Structured的。


做好之后,運行程序是這樣的效果:
然后再來實現中間那個視圖DepartmentDetailView的功能。在這個視圖中使用了一個TableViewer。使用TableViewer比使用ListViewer和TreeViewer要稍微復雜一點,因為需要我們自己添加表格的列,并設置表格的屬性。和SelectionView一樣,DepartmentDetailView實現ISelectionListener,并把自己注冊到Workbench中。代碼如下:

TableViewer依然需要一個ContentProvider和一個LabelProvider,不過沒有ITableContentProvider,因為TableViewer是把每一行當成一個element,所以它只需要和ListViewer一樣的ContentProvider即可。但是TableViewer需要的LabelProvider不一樣,要實現ITableLabelProvider,在這里,TableViewer才處理不同的列。如下圖:


完成后,運行程序是這樣的效果:

根據用戶的選擇決定工具欄按鈕是否可用
終于進入今天的最后一個議題了。 在前面的幾篇中,我陸續用過主菜單、視圖工具欄,今天用到了主工具欄。菜單和工具欄是GUI程序產生命令的主要方式,還有就是快捷鍵和彈出菜單。在Eclipse中,它們的本質都一樣,都是通過關聯到一個command和一個handler來實現其功能,通過一個menuContribution來指定它們的位置。在今天的示例中,我把menuContribution的locationURI設置為toolbar:org.eclipse.ui.main.toolbar,所以這兩個按鈕就出現在了主工具欄中。如下圖:

在今天的示例中加入工具欄按鈕可不是為了將工具欄。今天的主題是結構化數據。StructuredSelection也是結構化的數據。所以在這個示例中加入工具欄是為了展示用戶選擇的數據會對工具欄按鈕的行為產生影響。從功能上講,我們希望當用戶選擇一個科室的時候,添加病人的按鈕可用,而選擇多個科室或選擇一個或多個病人的時候,添加病人工具欄不可用,希望當用戶選擇一個病人的時候,刪除病人按鈕可用,而當選擇科室或多個病人的時候,刪除按鈕不可用。
這樣的需求在GUI編程中可以算是無處不在。Eclipse RCP是如何處理這個問題的呢?答案就是Workbench Core Expressions。下圖是Eclipse幫助文檔中關于Workbench Core Expressions的頁面:

然后,我們只需要對工具欄的按鈕添加如下的Workbench Core Expressions即可:

收工,今天就寫到這里。這篇文章已經夠長了,截圖也有20多張了,所以實現那兩個按鈕的Handler我就不寫了。理解了結構化數據和理解了IStructuredSelection后,理解Workbench Core Expression就很容易了,更詳細的內容大家自己看幫助文檔吧。
寫到這里,Eclipse RCP的大部分UI元素如窗口、菜單、工具欄、視圖等等什么的都提到了,還有一個很重要的大件的UI building block就是編輯器了。等下次有時間,我再來詳細討論編輯器的編寫。
Eclipse RCP詳解(04):Eclipse RCP相關的學習資料及國內相關圖書點評
前面幾篇都是使用Ubuntu系統及其自帶的Eclipse 3.8.1。在這一篇里,我將為大家展示Fedora 20和其自帶的Eclipse 4.3.1。同時,為了方便我隨時切換操作系統(Ubuntu、Fedora、Win7),我在GitHub上注冊了一個賬號,并把示例代碼放了上去。我切換系統后,只需要Fetch一下,就可以繼續開發。當然,有興趣看我代碼的朋友,也可以clone一份到自己的機器上。地址:
https://github.com/littleloach/examples.git
關于Eclipse和Git的整合,請看這里:淺論Maven和Git的原理及展示其與Eclipse的集成
事實證明,Eclipse RCP不僅是跨平臺的,而且是跨版本的。也就是說,即使我在Fedora中使用了不一樣的Eclipse版本,我在Ubuntu中寫的代碼到Fedora中也不需要修改。(我在Win7中用的Eclipse版本和Ubuntu中的相同。)
Fedora 20和Fedora 19都是我喜歡的版本。因為它們的開發代號分別為Heisenberg和薛定諤的貓,不難看出,它們都和量子力學有關,對我這樣的理工男吸引力那肯定是巨大的。而Heisenberg更具有傳奇性。不僅因為他是量子力學的主要創始人和諾貝爾獎獲得者,更因為歷史上到現在都沒有定論的海森堡謎題:為什么當初他沒有造出原子彈,是他真的搞錯了,還是他故意搞錯了?故事是這樣的:第二次世界大戰開始后,迫于納粹德國的威脅,丹麥的大物理學家玻爾離開了心愛的哥本哈根理論物理研究所,離開了朝夕相處的來自世界各地的同事,遠赴美國。德國的許多科學家也紛紛背井離鄉,堅決不與納粹勢力妥協。然而,有一位同樣優秀的物理學家卻留下來了,并被納粹德國委以重任,負責領導研制原子彈的技術工作,遠在異鄉的玻爾憤怒了,他與這位過去的同事產生了尖銳的矛盾,并與他形成了終生未能化解的隔閡。有趣的是,這位一直未能被玻爾諒解的科學家卻在1970年獲得了“玻爾國際獎章”,而這一獎章是用以表彰“在原子能和平利用方面做出了巨大貢獻的科學家或工程師”的。歷史在此開了個巨大的玩笑,這玩笑的主人公就像他發現的“測不準原理”一樣,一直讓人感到困惑和不解。他就是量子力學的創始人——海森堡。也許他當初就是故意留下來而且故意造不出原子彈的吧。
在正式介紹JFace和結構化數據之前,先向大家展示幾張Fedora的截圖。
第一張,Fedora剛安裝完成后,其Gnome桌面是很丑的:
所以需要進行一些Art Work,比如多安裝幾個wallpaper啦,多安裝幾個theme啦,然后安裝一個gnome-tweak-tool更改一下字體、字體微調和抗鋸齒啦。Fedora 19和20還有一個地方比較令人蛋疼,那就是它提供的gnome-terminal是不支持半透明背景的,如果想要半透明背景,只有自己編譯gnome-terminal。我太懶,所以只好把KDE中的konsole借來用用了。好在KDE程序在gnome中也可以正常運行。下面是經過我折騰后的Fedora 20:
有人說,GTK+程序的列表兩行之間的寬度大到可以塞進一頭大象。以前在Ubuntu下用Eclipse不覺得,換到Fedora中發現這個寬度確實是挺大的,看著很不舒服。不過這都不是個事,換個GTK+的theme一切都搞定。下面一張是Eclipse的截圖,還是很漂亮的說:
下面開始今天的正題。
結構化數據
結構化數據可不是我一拍腦袋想出來的術語。平時我們在組織和保存數據的時候,離不開數據結構。我們經常使用的數組、鏈表或多維數組來保存數據,也經常使用數據庫的表格保存數據,但是我們并不是時時刻刻想著結構化這么一個概念。但是在Eclipse中就不一樣了,結構化數據是它的一個很重要的理念,只要有可能,它都會把數據組織成結構化的。比如,如果你在Eclipse中選擇了一些什么,那么這些選擇的東西就是以IStructuredSelection展示出來的。如下圖,可見Structured無處不在:
數組(包括鏈表)、樹和表格(包括多維數組)是我們常用的數據結構。List控件、Tree控件和Table控件是我們常用的展示數據的控件。然而邏輯和形式并不總是一一對應的。比如形式上可以用Tree控件表現的數據,邏輯上并不一定是用樹結構組織起來的數據。想想我們做數據庫的ORM映射,如果一個表中的一行數據和另一個表中的多行數據相關聯(一對多映射),寫成Java Entity類的時候就是一個Entity中包含有一個另一個Entity的List或Set。這兩個Entity各有各的Class Name,而不是統一叫TreeNode。這兩個Entity之間也不用getParent和getChildren互相訪問。但是從形式上,依然可以用一個Tree控件來展示它們,不是嗎?
JFace中的Viewer
在該系列博文的第02篇中,我展示了SWT的List控件和Tree控件。這兩個控件的使用是很簡單的,List控件只需要使用add()方法就可以在列表中添加一個字符串,Tree控件需要先構造一個TreeItem對象,然后用setText()方法添加一個字符串??丶荒苡脕盹@示字符串和字符串前面的一個小圖標,不能保存復雜的數據。MVC是常用的設計模式,Model用來保存數據,而控件只是負責顯示,所以中間一定要有一個機制將Model中的數據轉換為能讓控件顯示的字符串和圖標,并且要能維持數據在結構化上的邏輯聯系。
JFace中的Viewer是對SWT中的控件的封裝,它使用的正是MVC模式。如果不了解Viewer的哲學,使用Viewer就無從下手。Viewer的哲學是這樣的:Viewer對它要顯示的數據是怎么組織的沒有任何要求,可以使用它的setInput()方法將任何一個對象作為它的Model;很顯然Viewer不可能智能到顯示任何結構的數據,所以必須給它提供一個ContentProvider,ContentProvider就是負責將任意類型的Model轉化為結構化的數據;Model經過ContentProvider轉化為結構化數據后,Viewer再對該結構中的每一項進行顯示,前面提到過控件只能顯示文字和圖片,所以還需要一個LabelProvider來將數據轉化成要顯示的文字和圖片。
由上面的描述可以看出,要使用Viewer必須最少編寫三樣東西:一些Model、一個ContentProvider和一個LabelProvider。當然,Viewer還有更多的功能,比如對數據進行排序和過濾,只需要提供相應的SortProvider和FilterProvider即可,但這兩個Provider不是必須的。雖然要寫的類比較多,但是JFace都定義好了Interface,我們只需要實現即可,在IDE的幫助下,這個工作一點也不難。
一個簡單的示例
文字內容寫得再多,也不如一個例子來得直接。我這里模擬HIS(Hospital Information System)系統中一個簡單的功能來展示Viewer的用法。在醫院里,一個住院部(Inpatient Department)可以有很多個科室(Department),比如內科、外科、五官科等,一個科室里面可以有很多個病人(Patient)。很顯然,這些數據用一個TreeViewer顯示是再合適不過了。先看看這個示例程序界面的總體框架,如下圖:
這只是一個框架,結合上面的界面,我簡要說說我要實現的功能:
1、最左邊的視圖使用了一個TreeViewer,用來顯示住院部的所有科室和病人;
2、工具欄有兩個按鈕,如果最左邊的視圖中被選擇的對象是科室,則帶加號的按鈕可用,其功能是為該科室增加一個病人;如果最左邊的視圖中被選擇的對象是病人,則帶減號的按鈕可用,其功能是刪除這個病人;
3、中間的視圖使用一個TableViewer控件,當最左邊的視圖中被選擇的對象是科室時,則以表格的形式顯示該科室所有病人的詳細信息,每一行代表一個病人;
4、最右邊的視圖使用一個ListViewer控件,僅僅顯示當前選擇的內容。
為示例程序準備數據
該示例程序用到的數據是一個住院部有多個科室、一個科室有多個病人,很顯然這是一個典型的一對多的關系。所以我們的Model需要三個類:Inpatient、Department、Patient。其關系如下:
其中的Gender是一個枚舉。下面是它們的代碼:




然后,創建一個PatientView類,在里面使用一個TreeViewer控件,創建一個Inpatient類的實例,然后添加一點點測試數據,然后把Inpatient的實例作為viewer的input即可。如下圖:
當然,如果沒有設置好ContentProvider和LabelProvider,數據是不可能正常顯示的。所以我們還需要創建一個PatientContentProvider類和PatientLabelProvider類。好在JFace中有早就定義好的接口,我們只需要從這些接口實現即可,如下圖:
我們先來看PatientContentProvider需要實現哪些方法,如下圖,可以看出IDE已經自動把框架搭建起來了,我們只需要填空即可(下圖中的空是我已經填好了的):
第一個要實現的方法是getElements,該方法要求我們把Input的數據轉化成一個Object數組返回。在這個例子中,Input進來的是Inpatient類的對象,所以只需要先將inputElement轉型為Inpatient,然后返回它里面的Department數組即可。第二個要實現的方法是hasChildren,在本例中,如果element是Department,則它就有children,如果是Patient就沒有children。最后要實現的一對方法就是getChildren和getParent,很顯然,如果element是Department,則它的children是它里面保存的List<Patient>,不過返回的時候要把它轉化成數組,如果element是Patient,則它的parent是它的department。So easy!不是嗎?
而PatientLabelProvider的實現就更簡單了,如下圖:
看圖填空,我們只需要完成getText方法和getImage方法即可。邏輯也很簡單,就是判斷element是Department還是Patient,然后分別返回相應的字符串和圖片即可。在這里,我還故意把不同的性別顯示為不同的圖標。有了截圖,我就不貼代碼了。
俗話說一通百通,使用不同的Model和Viewer,就給它不同的ContentProvider和LabelProvider即可。
處理Selection事件
關于數據的展示,我們輕松搞定。不過這還不算完,還有工作要做?,F在數據顯示出來了,我們就可以用鼠標來點擊,來選擇。那么我們選擇的數據怎么樣來影響我們程序的行為呢?在前面幾篇對GUI的探討中我說過,程序和用戶的交互是通過事件處理來進行的。也就是說,當我們選擇了Viewer中的一項或多項的時候,Viewer應該發送一個Selection事件,我們向程序中注冊一個SelectionListener就應該可以捕獲到這個事件。
為了讓patientViewer向程序發送Selection事件,我們需要讓它成為程序的selectionProvider。通過在PatientView中添加如下一行代碼(最后一行):
有了發送Selection事件的源,接著應該有接受Selection事件的Listener。先從最簡單的功能做起。最右邊的視圖只是用來顯示所選擇的對象,所以從它先開始。最右邊的視圖類是SelectionView,里面用了一個ListViewer,在這里,我們先讓SelectionView實現ISelectionListener接口,然后把它注冊到Workbench中。如下圖,關鍵代碼我把它標出來了:
selectionChanged()方法將在有目標被選中的時候調用,該方法的實現也很簡單,就是把selection作為ListViewer的input,讓它顯示即可。而要使用ListViewer,我們又得為它提供一個ContentProvider和一個LabelProvider。下面是ContentProvider和LabelProvider的實現,也很簡單,這次ContentProvider實現了IStructuredContentProvider接口,而且從代碼中可以看出,selection是一個IStructuredSelection,哪怕我們只選中一項,它也是Structured的。
做好之后,運行程序是這樣的效果:
然后再來實現中間那個視圖DepartmentDetailView的功能。在這個視圖中使用了一個TableViewer。使用TableViewer比使用ListViewer和TreeViewer要稍微復雜一點,因為需要我們自己添加表格的列,并設置表格的屬性。和SelectionView一樣,DepartmentDetailView實現ISelectionListener,并把自己注冊到Workbench中。代碼如下:
TableViewer依然需要一個ContentProvider和一個LabelProvider,不過沒有ITableContentProvider,因為TableViewer是把每一行當成一個element,所以它只需要和ListViewer一樣的ContentProvider即可。但是TableViewer需要的LabelProvider不一樣,要實現ITableLabelProvider,在這里,TableViewer才處理不同的列。如下圖:
完成后,運行程序是這樣的效果:
根據用戶的選擇決定工具欄按鈕是否可用
終于進入今天的最后一個議題了。 在前面的幾篇中,我陸續用過主菜單、視圖工具欄,今天用到了主工具欄。菜單和工具欄是GUI程序產生命令的主要方式,還有就是快捷鍵和彈出菜單。在Eclipse中,它們的本質都一樣,都是通過關聯到一個command和一個handler來實現其功能,通過一個menuContribution來指定它們的位置。在今天的示例中,我把menuContribution的locationURI設置為toolbar:org.eclipse.ui.main.toolbar,所以這兩個按鈕就出現在了主工具欄中。如下圖:
在今天的示例中加入工具欄按鈕可不是為了將工具欄。今天的主題是結構化數據。StructuredSelection也是結構化的數據。所以在這個示例中加入工具欄是為了展示用戶選擇的數據會對工具欄按鈕的行為產生影響。從功能上講,我們希望當用戶選擇一個科室的時候,添加病人的按鈕可用,而選擇多個科室或選擇一個或多個病人的時候,添加病人工具欄不可用,希望當用戶選擇一個病人的時候,刪除病人按鈕可用,而當選擇科室或多個病人的時候,刪除按鈕不可用。
這樣的需求在GUI編程中可以算是無處不在。Eclipse RCP是如何處理這個問題的呢?答案就是Workbench Core Expressions。下圖是Eclipse幫助文檔中關于Workbench Core Expressions的頁面:
然后,我們只需要對工具欄的按鈕添加如下的Workbench Core Expressions即可:
收工,今天就寫到這里。這篇文章已經夠長了,截圖也有20多張了,所以實現那兩個按鈕的Handler我就不寫了。理解了結構化數據和理解了IStructuredSelection后,理解Workbench Core Expression就很容易了,更詳細的內容大家自己看幫助文檔吧。
寫到這里,Eclipse RCP的大部分UI元素如窗口、菜單、工具欄、視圖等等什么的都提到了,還有一個很重要的大件的UI building block就是編輯器了。等下次有時間,我再來詳細討論編輯器的編寫。