GEF的設計沒有對模型部分做任何限制,也就是說,我們可以任意構造自己的模型,唯一須要保證的就是模型具有某種消息機制,以便在發生變化時能夠通知GEF(通過EditPart)。在以前的幾個例子里,我們都是利用java.beans包中的PropertyChangeSupport和PropertyChangeListener來實現消息機制的,這里將介紹一下如何讓GEF利用EMF構造的模型(下載例子,可編輯.emfsubject文件,請對比之前功能相同的非EMF例子),假設你對EMF是什么已經有所了解。
EMF使用自己定義的Ecore作為元模型,在這個元模型里定義了EPackage、EClassifier、EFeature等等概念,我們要定義的模型都是使用這些概念來定義的。同時因為ecore中的所有概念都可以用本身的概念循環定義,所以ecore又是自己的元模型,也就是元元模型。關于ecore的詳細概念,請參考EMF網站上的有關資料。
利用EMF為我們生成模型代碼可以有多種方式,例如通過XML Schema、帶有注釋的Java接口、Rose的mdl文件以及.ecore文件等,EMF的代碼生成器需要一個擴展名為.genmodel的文件提供信息,這個文件可以通過上面說的幾種方式生成,我推薦使用Omondo公司的EclipseUML插件來構造.ecore文件,該插件的免費版本可以從這里下載。
圖1 示例模型
為了節約篇幅和時間,我就不詳細描述構造EMF項目的步驟了,這里主要把使用EMF與非EMF模型的區別做一個說明。圖1是例子中使用的模型,其中Dimension和Point是兩個外部java類型,由于EMF并不了解它們,所以定義為datatype類型。
使用兩個Plugins
為了讓模型與編輯器更好的分離,可以讓EMF模型單獨位于一個Plugin中(名為SubjectModel),而讓編輯器Plugin(SubjectEditor)依賴于它。這樣做的另一個好處是,當修改模型后,如果你愿意,可以很容易的刪除以前生成的代碼,然后全部重新生成。
EditPart中的修改
在以前我們的EditPart是實現java.beans.PropertyChangeListener接口的,當模型改用EMF實現后,EditPart應改為實現org.eclipse.emf.common.notify.Adapter接口,因為EMF的每個模型對象都是Notifier,它維護了一個Adapter列表,可以把Adapter作為監聽器加入到模型的這個列表中。
實現Adapter接口時須要實現getTarget()和setTarget()方法,target代表發出消息的那個模型對象。我的實現方式是在EditPart里維護一個Notifier類型的target變量,這兩個方法分別返回和設置該變量即可。
還要實現isAdapterForType()方法,該方法返回一個布爾值,表示這個Adapter是否應響應指定類型的消息,我的實現一律為"return type.equals(getModel().getClass());"。
另外,propertyChanged()方法的名稱應改為notifyChanged()方法,其實現的功能和以前是一樣的,但代碼有所不同,下面是NodePart中的實現,看一下就應該明白了:
還有active()/deactive()方法中的內容需要修改,作用還是把EditPart自己作為Adapter(不是PropertyChangeListener了)加入模型的監聽器列表,下面是SubjectPart的實現,其中eAdapters()得到監聽器列表:
可以看到,我們對EditPart所做的修改實際是在兩種消息機制之間的轉換,如果你對以前的那套機制很熟悉的話,這里理解起來不應該有任何困難。
ElementFactory的修改
這個類的作用是根據template創建新的模型對象實例,以前的實現都是"new XXX()"這樣,用了EMF以后應改為"ModelFactory.eINSTANCE.createXXX()",EMF里的每個模型對象實例都應該是使用工廠創建的。
使用自定義CreationFactory代替SimpleFactory
在原先的PaletteFactory里定義CreationEntry時都是指定SimpleFactory作為工廠,這個類是使用Class.newInstance()創建新的對象實例,而用EMF作為模型后,創建實例的工作應該交給ModelFactory來完成,所以必須定義自己的CreationFactory。(注意,示例代碼里沒有包含這個修改。)
處理自定義數據類型
我們的Node類里有兩個非標準數據類型:Point和Dimension,要讓EMF能夠正確的將它們保存,必須提供序列化和反序列化它們的方法。在EMF為我們生成的代碼里,找到ModelFactoryImpl類,這里有形如convertXXXToString()和createXXXFromString()的幾個方法,分別用來序列化和反序列化這種外部數據類型。我們要把它的缺省實現改為自己的方式,下面是我對Point的實現方式:
注意,修改后要將方法前面的@generated注釋刪除,這樣在重新生成代碼時才不會被覆蓋掉。要設置使用這些類型的變量的缺省值會有點問題(例如設置Node類的location屬性的缺省值),在EMF自帶的Sample Ecore Model Editor里設置它的defaultValueLiteral為"100,100"(這是我們通過convertPointToString()方法定義的序列化形式)會報一個錯,但不管它就可以了,在生成的代碼里會得到這個缺省值。
保存和載入模型
EMF通過Resource管理模型數據,幾個Resource放在一起稱為ResourceSet。前面說過,要想正常保存模型,必須保證每個模型對象都被包含在Resource里,當然間接包含也是可以的。比如例子這個模型,Diagram是被包含在Resource里的(創建新Diagram時即被加入),而Diagram包含Subject,Subject包含Attribute,所以它們都在Resource里。在圖1中可以看到,Diagram和Connection之間存在一對多的包含關系,這個關系的主要作用就是確保在保存模型時不會出現DanglingHREFException,因為如果沒有這個包含關系,則Connection對象不會被包含在任何Resource里。
在刪除一個對象的時候,一定要保證它不再包含在Resource里,否則保存后的文件中會出現很多空元素。比較容易犯錯的地方是對Connection的處理,在刪除連接的時候,只是從源節點和目標節點里刪除對這個連接的引用是不夠的,因為這樣只是在界面上消除了兩個節點間的連接線,而這個連接對象還是包含在Diagram里的,所以還要調用從Diagram對象里刪除它才對,DeleteConnectionCommand中的代碼如下:
當然,新建連接時也不要忘記將連接添加在Diagram對象里(代碼見CreateConnectionCommand)。保存和載入模型的代碼請看SubjectEditor的init()方法和doSave()方法,都是很標準的EMF訪問資源的方法,以下是載入的代碼(如果是新創建的文件,則在Resource中新建Diagram對象):
雖然到目前為止我還沒有機會體會EMF在模型交互引用方面的優勢,但經過進一步的了解和在這個例子的應用,我對EMF的印象已有所改觀。據我目前所知,使用EMF模型作為GEF的模型部分至少有以下幾個好處:
此外,EMF.Edit框架能夠為模型的編輯提供了很大的幫助,由于我現在對它還不熟悉,所以例子里也沒有用到,今后我會修改這個例子以利用EMF.Edit。
Powered by: BlogJava Copyright © sharky的點滴積累