EMF除了生成模型部分的接口和實現類(不妨稱作“核心模型”)以外,還生成一個名稱 以.Edit結尾的項目,包含一些與核心模型和編輯器關系都十分緊密的代碼。這部分代碼經過 了精心設計,可重用的程度是相當的高。它們不僅在EMF生成的編輯器項目裡大量被用到,我 們自己在擴展編輯器的時候也應該充分利用。
在線商店的例子裡,com.my.shop.edit項目裡包含一個ItemProviderAdapterFactory類和 一組 ItemProviderAdapter的子類,後者是和核心模型的接口一一對應的,例如核心模型的 Shop、Category和Product分別對應 ShopItemProvider、CategoryItemProvider和 ProductItemProvider。這篇帖子主要介紹一下這些 ItemProvider,而關於 ItemProviderAdapterFactory的內容將在以後的帖子裡專門介紹,其實顧名思義, ItemProviderAdapterFactory的作用主要就是生成ItemProvider。事實上在構造EMF應用程序 時,我們經常要修改 ItemProvider裡的代碼,而ItemProviderAdapterFactory則很少改動。
圖1 EMF生成的.Edit項目
注意:.Edit項目裡ItemProviderAdapter的子類名稱裡省略了Adapter這個單詞,例如 CategoryItemProvider而非CategoryItemProviderAdapter,你心裡應該清楚它是一個 Adapter,因為它確實實現了Adapter接口。EMF裡另外專門有一個ItemProvider類是為非 Adapter類型准備的,在這篇裡說的 ItemProvider不是指它,而是指XXXItemProvider,也就 是ItemProviderAdapter的子類。
注意:EMF裡的Adapter接口和Eclipse Runtime的IAdaptable接口雖然名稱相似,但並不 是同一個概念(關於IAdaptable請參見前面的翻譯帖子), EMF裡的Adapter等同於監聽器( Listener、Observer)的作用,它監聽的對象是EMF的Notifier,在一個Notifier 上可以注 冊多個Adapter。另一方面,ItemProviderAdapterFactory則很像IAdaptable,它們都能夠起 到動態轉換類型的作用,只不過前者一般只用於Notifier到Adapter的轉換,後者則沒有什麼 限制,此外轉換方法的名稱也不同,前者是adapt(),後者為 getAdapter()。
從圖1中不難看出,ItemProvider構成了.Edit項目的主要部分,這些ItemProvider具有以 下幾個作用。
一、實現了JFace中ContentProvider和LabelProvider的功能
JFace查看器(Viewer)是對swt中控件的一種包裝,例如TableViewer是對Table的包裝, TreeViewer是對Tree的包裝,等等,通過這種方式可以將控件與顯示在控件中的數據在一定 程度上分離,從而方便數據顯示的更新。相當多的Eclipse應用程序都是通過JFace查看器顯 示數據的,與查看器關聯的ContentProvider和LabelProvider分別控制查看器中顯示的哪些 數據以及每條數據的顯示方式。
以TreeViewer的ContentProvider為例,在JFace裡應該實現ITreeContentProvider接口, 這個接口定義了getParent()、hasChildren()和getChildren()這三個方法;在EMF裡有 ITreeItemContentProvider接口與之對應,這個接口同樣具有這三個方法,.Edit部分的每個 ItemProvider都實現了這個接口,因為EMF已經完全知道我們的模型結構,所以這三個方法在 ItemProviderAdapter類裡已經實現好了。不過 ITreeItemContentProvider畢竟不能直接交 給JFace的TreeViewer來使用,所以EMF提供了一個 AdapterFactoryContentProvider來做適 配工作,你可以在編輯器的代碼裡看到如何使用它。
LabelProvider也是類似的,它主要控制顯示的文字和圖標。EMF生成的ItemProvider缺省 沒有實現 ITableItemLabelProvider,所以如果要使用TableViewer,要修改代碼以實現 ITableItemLabelProvider接口和額外的方法,具體請參考在線商店例子中的 ProductItemProvider。從 JFace的角度來說,ItemProvider相當於集成了各種查看器的 ContentProvider和LabelProvider的代碼,是一個通用的“ContentLabelProvider”。因此 利用它,開發人員在改變查看器的時候只需要修改很少的代碼,而不像傳統方式那樣每換一 個查看器還要寫新的ContentProvider和LabelProvider。
二、提供了關聯對象的屬性表
每個ItemProvider的getPropertyDescriptors()方法返回在屬性視圖裡顯示的屬性列表, 列表裡的每個元素是一個 ItemPropertyDescriptor對象,它決定了每個屬性的標簽、描述、 圖標以及是否可編輯。EMF為生成的代碼會幫我們把模型定義裡的每個屬性都顯示在屬性列表 裡,如果希望隱藏某些屬性,可以通過修改這個方法移除之。
以Product為例,ProductItemProvider的getPropertyDescriptors()方法裡包含這樣六條 語句,分別代表產品名稱、價格、描述、是否有貨、評價以及顏色這六個屬性,如果你想讓 顏色屬性在屬性列表裡消失,只要刪除最後一句即可。
addNamePropertyDescriptor(object);
addPricePropertyDescriptor(object);
addDescriptionPropertyDescriptor(object);
addAvaiablePropertyDescriptor(object);
addScorePropertyDescriptor(object);
addBackgroundPropertyDescriptor(object);
三、生成編輯模型的各種命令
在ItemProviderAdapter基類裡有很多createXXXCommand()方法,如果你用過GEF應該對這 些名稱不陌生,因為在 EditPolicy裡也有類似的方法。我們知道,為了實現Undo/Redo功能 ,對模型的每個改變都應該使用Command實現,然後把 Command保存在Command棧裡,每個 Command對象保存Undo/Redo自己的信息。ItemProviderAdapter相當於一個生產這些Command 的工廠,用戶對模型編輯的請求都將通過它轉換為對應的Command,例如用戶在屬性視圖裡修 改了一個屬性的值,當按下回車後,會調用該對象關聯的ItemProvider類的 createSetCommand()方法生成一個SetCommand對象。
注意:在createCommand()方法裡會調用getChildrenFeatures()方法,而在實現 ContentProvider的getChildren()時也需要這個方法,因此這個方法的返回結果同時影響 ItemProvider的這兩項功能。
四、將模型的改變通知到負責顯示模型的視圖
在一個Eclipse應用程序裡經常會有很多個查看器顯示模型,無論用戶怎樣修改模型,要 讓這些查看器裡顯示的內容總是當前的模型,最好的辦法是讓查看器能夠響應模型的變化。 ItemProvider作為監聽器可以很好的完成這個任務。
模型發生改變時,與被修改的對象相關聯的ItemProvider的notifyChanged()方法被調用 ,事件立即被通知給 ItemProviderAdapterFactory,後者是整個模型的事件處理機構,所有 的ItemProvider都是通過 ItemProviderAdapterFactory創建並注冊為監聽器的,因此 ItemProviderAdapterFactory可以把事件通過 fireNotifyChanged()通知給所有這些監聽器 的notifyChanged()方法去消化。圖2展示了這個通知過程,此圖來自《Eclipse Modeling Framework: A Developer's Guide》第3.2.4節中的圖3.10。
圖2 ItemProvider的通知流程
最後,ItemProvider還有一個collectNewChildDescriptors()方法,這個方法決定了在編 輯器裡,模型裡對應的那個對象可以創建哪些子元素。例如在線商店模型裡,Category對象 的子元素是Category和Product,那麼用戶在編輯器裡右鍵點擊一個 Category對象選擇“New Child”時,就會出現“Category”和“Product”這兩個選項。有些場合我們想隱藏其中一 些選項時,就可以修改這裡的代碼。