使用 GMF 快速開發支持嵌套圖元的編輯器

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  
GMF 是 Eclipse 項目中支持快速開發圖形界面的工具包,本文介紹如何使用 GMF 建立嵌套圖元來表達層次模型。結合應用示例,從建立領域模型、開發圖形定義、工具定義、映射信息直到生成插件代碼,詳細描述了整個開發過程和其中容易疏忽的問題,並給出對應大綱視圖的開發方法。

摘要

GMF(Graphical Modeling Framework)是 Eclipse 項目中支持快速開發圖形界面的工具包,但是目前相關資料並不豐富,這給開發人員學習和實踐帶來了一定困難。實際應用中往往需要繪製帶有層次關係的模型,由於層次的出現,在表達領域模型、圖形繪製信息等方面就要額外處理。本文對比了建立層次模型的兩種方法,重點介紹其中的嵌套圖元方法。結合一個簡單應用,從建立領域模型、開發圖形定義、工具定義、映射信息直到生成插件代碼,詳細描述了整個開發過程和其中容易疏忽的問題,並給出圖示。為了輔助顯示模型層次,還進一步介紹了對應大綱視圖的開發方法。





前言

Graphical Modeling Framework 是 eclipse 項目中支持快速開發圖形界面的工具包,它有效地將 EMF 和 GEF 連接在一起,使得開發人員通過定製圖形、工具、映射等抽象信息就可以快速創建出功能強大的 Eclipse 圖形插件。

實際中往往需要繪製帶有層次關係的模型,這相比開發只有一層的模型更為複雜。對於軟體開發人員而言,最熟悉的場景莫過於在設計時為系統劃分功能模塊,繼而設計代碼包結構。“包”中包含代碼“文件”,也可能包含其它“包”,這就構成了項目的層次結構。在同一個“包”中,“文件”之間的關係比較緊密,但由於系統複雜性,不同“包”的“文件”之間往往也存在依賴關係。怎樣開發可以表達這種層次關係的編輯器呢?本文重點介紹使用 GMF 建立嵌套圖元的方法。

嵌套圖元是指通過相互嵌套的圖元來表達層次關係,即外層圖元作為容器,包含內層圖元,從而直觀的表達模型的層次結構。這種方法的特點是將不同層次的圖元繪製在一個平面上,可以方便的建立各層次圖元之間的關聯,使得模型的表達方式更加靈活。對於前面提到的應用場景,嵌套圖元方法比較適用。可以將“包”作為容器,“文件”作為最小單位的圖元;容器可以相互嵌套,也可以包含最小圖元,不同容器中的圖元可以根據需要建立關聯。不過,由於嵌套圖元模型中既有全局信息,又有內部細節,複雜度不易控制,適用於規模較小的分散式系統。

與嵌套圖元相比,還有另外一種方法可以表達層次關係,即將主模型和子模型相分離,主模型關注高層次的抽象圖元,子模型則描述細節。由於主/子模型截然分開,難以表達模塊間的複雜關係,適用於鬆散耦合的系統。兩種方法如下圖所示。後者在[3]中已有詳細介紹,本文主要關注嵌套圖元方法。

(A)通過嵌套圖元表達層次關係 (B)主模型與子模型相分離

使用 GMF 技術開發編輯器的過程如下圖所示。


圖 1. GMF 項目開發過程

首先創建 GMF 項目,分別然後建立領域模型、圖形定義和工具定義,其中領域模型相當於業務的元模型,定義主要建模元素及其關係;圖形定義用於表達各元素的圖形繪製信息;工具定義則給出建立模型時需要的工具。在此基礎上開發映射模型,將圖形定義、工具定義與領域模型連接起來。繼而得到生成模型,最後自動產生插件代碼。以插件方式運行,即可以完成圖形編輯器的基本功能了。

下面本文按照以上的開發順序,介紹如何通過容器嵌套來表達圖元的層次關係。





定義領域模型

領域模型是建模的基礎,要想表達圖元的嵌套關係,首先需要在該模型中建立包含(composition)關係。有了包含關係,在編輯器中包含的元素就體現為容器,被包含的元素體現為容器中的圖元,而容器也可以包含另一個容器。領域模型有多種表達方式,GMF 開發中要求EMF模型,而大多數開發人員更熟悉UML模型。為了便於建立需要的領域模型,可以先使用 UML 工具建立類圖,而後轉化得到 EMF 模型。對於包含而言,UML 區分了組合 composition 和聚合 aggregation,在類圖上體現為實心的菱形和空心的菱形,它們之間的區別在講解面向對象技術的資料上都可以找到。而 EMF 模型沒有做這種區分,它通過包含 contain 來表達,對應於 UML 中的組合關係。也就是說,如果 UML 模型用來轉化生成 EMF 模型,就需要使用組合關係而不是聚合關係。

我們對前面的應用場景進行抽象,以便應用到更多的場景中。在目標模型中,“容器 Container”對應“包”,“實體 Entity”對應“文件”,“連接 Link”對應它們之間的依賴關係。於是我們定義如下的類圖。


圖 2. 類圖-領域模型

在該模型中,類 Diagram 表示整個模型圖,類 Node 和 Link 分別表示節點和它們之間的有向連線。從類 Node 派生得到類 Container 和 Entity,分別表示可以容納其他節點的容器和不可再分的最小圖元。而且,Link 通過 source 和 target 記錄了起始節點和終止節點;相應的,Node 通過 outgoing 和 incoming 記錄了發出和進入的連線。

[5]中已經介紹了如何藉助 EclipseUML 插件建立 UML 模型,繼而轉換為 EMF 模型。本文不再贅述,生成的 EMF 模型如下圖所示。

基於 EMF 模型,可以自動產生模型代碼和編輯代碼,前者定義了該模型中聲明的類及其關聯,後者定義了與這些類相關的繪圖編輯信息。





定義圖形信息

容器節點可以包含若干節點,在圖形定義中我們需要使用適當的元素來表達,這裡介紹兩種方法。一種是使用 GMF2.0 新引入的元素——帶標籤的容器(Labeled Container),另一種是使用套件(Compartment)。前者本身就是表示容器的圖元,它可以容納其他圖元,並且帶有標籤以顯示標題。後者也作為容器,但是它不能單獨使用,需要依附在某個圖元上,並作為該圖元的子元素存在。兩者的開發和使用方法各不相同,這體現在本節的圖形定義以及後面的映射定義等,本文將針對這兩種方法分別進行介紹。

建立圖形定義可以使用創建嚮導,依次設定文件的位置和名稱、相關聯的領域模型和對應模型圖的類元素,最後通過選擇框指明領域模型中的哪些類作為繪圖節點,哪些類或引用作為連線,哪些屬性作為標籤。點擊完成按鈕就可以建立基本的圖形框架,而後再根據需要調整圖形信息。

圖形定義——帶標籤的容器

圖形定義中需要定義圖形庫和圖元對象,圖形庫只關注繪圖效果,通過圖形描述符定義各種圖形的形狀、顏色等信息;而圖元對象表示可以被添加到畫布上的元素,它們使用圖形庫中的圖形來繪製。


圖 3. 使用帶標籤容器建立的圖形定義

在上圖中,被選中的元素 ContainerFigure 就是帶標籤的容器,其中還包含用來顯示標題的標籤。圖形庫中還有描述實體圖形的 EntityFigure 和描述連接圖形的 LinkFigure,折線修飾符 Arrow 表示箭頭,在 LinkFigure 的屬性中將其指定為連線目標端的修飾符號。另外,畫布中定義了圖元對象 Container、Entity 和 Link 等,分別使用圖形庫中的描述符 ContainerFigureDes、EntityFigureDes 和 LinkFigureDes。

圖形定義——套件

相比上面的方法,這裡還要在圖元對象部分增加套件對象的定義,以表明容器圖元又包含了套件圖元,其中再容納其他圖元。


圖 4. 使用套件建立的圖形定義

在上圖中,容器圖元使用圓角矩形,非容器圖元使用直角矩形。被選中的就是套件圖元,可以為其定製屬性:Collapsible 表明是否可以摺疊,Figure 指定所依賴的圖形。它放置在容器圖元上,因此指定圖形為 ContainerFigureDes。





定義工具信息

工具信息用於配置調色板上的工具按鈕。工具可分為標準工具、創建工具和通用工具,標準工具提供繪圖中常用的選中、放大/縮小等功能,創建工具就是用來創建圖元的,而通用工具沒有功能限制,通過指定自定義的工具類來實現。GMF 框架已經內置了標準工具,一般情況下無需再添加,這裡主要提供容器 Container、實體圖元 Entity 和連接 Link 的創建工具。


圖 5. 工具定義

在上圖中,將節點和連接分成兩個工具組,中間用間隔符隔開。然後對應定義了三個創建工具,並使用了預設的圖標,如果需要替換為自定義圖標,則需要使用 Bundle Image 並指明圖標在項目中的相對路徑。





定義映射信息

映射信息是 GMF 開發中至關重要的步驟,它將前期開發的圖形定義、工具定義與 EMF 模型關聯在一起,使得通過圖形界面上的操作可以建立起所需的模型。根據內容不同,需要定義三類映射關係,即畫布映射、節點映射和連接映射。畫布映射在最外層定義了所建立的整個模型對應於領域模型中的哪個元素、圖形定義中的畫布以及工具定義中的調色板。類似的,節點映射/連接映射定義了對應的領域模型中的元素、圖形定義中的節點和工具定義中的工具。

定義映射信息也可以使用創建嚮導,依次設定文件的位置和名稱、相關聯的領域模型和最外層類、工具定義以及圖形定義文件,最後選擇在映射中哪些元素作為節點,哪些作為連接。點擊完成按鈕就可以得到基本的映射框架,而後再根據需要進行調整。

映射定義——帶標籤的容器

本例需要分別為畫布和兩個節點——Container 和 Entity,以及一個連接——Link 進行映射。對於節點映射,GMF 規定首先建立頂層節點引用,它表示可以直接在畫布中繪製的圖元。本例依次建立兩個這樣的頂層節點引用,之下各自建立節點映射。進一步的,由於容器節點可以容納其他節點,因此,我們在該節點映射之下再增加兩個子節點引用,分別對應容器和實體節點。值得注意的是,這種引用關係並不像前面的操作,再次建立新的節點映射,而是直接借用已經在上面建立起來的節點映射,只需在屬性“被引用子節點(Referenced Child)”中進行選擇。


圖 6. 使用帶標籤容器的映射定義

以容器節點為例,在頂層節點引用中,指定包含特性(Containment Feature)為 nodes:Node,這表示該頂層節點與根元素 Diagram 具有 nodes 關係。其中進一步定義節點映射,指定對應的 EMF 模型元素為 Container 類,使用圖形定義中的 Container 圖元來顯示,藉助工具定義中的 Container 工具來創建。該節點映射中進而定義兩個子節點引用,一個表示 Entity 節點,其包含特性為 elements:Node,被引用子節點為之前已經定義的 Entity 節點映射;另一個表示 Container 節點,其包含特性也是 elements:Node,被引用子節點是已定義的 Container 節點映射。下面簡要給出該編輯器中其他項目的屬性信息。

Canvas Mapping  	Domain Model: HierarchyDemo  	Element: Diagram  	Diagram Canvas: Canvas HierarchyDemo  Top Node Reference <nodes:Entity/Entity>  Containment Feature: nodes:Node  Node Mapping<Entity/Entity>  Element: Entity->Node  Diagram Node: Node Entity(EntityFigureDes)  Tool: Creation Tool Entity  Link Mapping  	Containment Feature: links:Link  	Element: Link  	Source Feature: source:Node  	Target Feature: target:Node  	Diagram Link: Connection Link  	Tool: Creation Tool Link  

映射定義——套件

相比上面的方法,映射定義主要的還是為容器節點、實體節點以及連接建立映射,但由於該方法的圖形定義中增加了套件圖元用於容納子圖元,所以還需要為其定義映射關係。


圖 7. 使用套件的映射定義

在上圖中,頂層節點引用和子節點引用的設置與第一種方法基本相同,區別在於套件圖元的映射。根據前面的介紹,套件實際上是容器節點的子元素,所以在映射定義中,套件的映射也應該定義在容器映射之中。圖中選中部分就是該套件映射,它對應圖形定義中的套件圖元,可以容納 Container 和 Entity 兩個子節點引用。實際上,這種包含關係也可以在子節點引用的屬性“套件 Compartment”中進行指定。





產生“代碼生成信息”

前面建立的映射信息可以自動產生一個特定文件,該文件稱為代碼生成文件,較詳細的描述了如何生成圖形編輯器的代碼。該文件中直接對應 EditPart,它是 GMF 最終代碼中的元素,代表顯示在編輯器中的圖元。從映射文件到代碼生成文件,再到最終代碼,是抽象層次不斷降低的結果,這與 Model Driven Architecture 具有相似的原則。

生成文件——帶標籤的容器


圖 8. 使用帶標籤容器的代碼生成文件

代碼生成文件描述了如何生成模型圖,包括生成頂層節點、子節點和連接。模型圖 DiagramEditPart 的 Viewmap 和容器 EditPart(包括 ContainerEditPart 和 Container2EditPart)的 Viewmap,描述如何在圖形上繪製被容納的內容,其布局屬性定義內容圖元的布局,預設的 UNKNOWN 以及 XY_LAYOUT 類型表示根據滑鼠位置來布局,並支持隨意拖動容器內的圖元位置。

代碼生成文件可以自動產生 GMF 代碼,預設情況下生成一個插件項目,並依賴於前面建立的 EMF 模型和編輯項目。至此,已經開發出一個滿足基本要求的圖形編輯器,以 Eclipse 插件方式運行,就可以進行新建、編輯、保存模型等常規操作。如下圖所示。


圖 9. 使用帶標籤容器實現的編輯器界面

生成文件——套件


圖 10. 使用套件的代碼生成文件

與上面的方法相比,代碼生成文件中增加了兩個套件對象,即 ContainerContainerComparmentEditpart 和 ContainerContainerCompartment2EditPart,分別對應頂層容器節點和子節點容器的套件 Editpart。

基於該生成文件,自動生成項目代碼,運行界面如下圖所示。


圖 11. 使用套件建立的編輯器界面

以上過程開發的 GMF 代碼只是建立了圖形編輯器的基本框架,而其他與應用有關的特定需求還要進行定製或開發。本文所關注的模型層次是在圖中通過嵌套圖元來表達,而樹形結構能夠更清晰的表達層次,往往作為模型圖的一種補充方式。這裡可以借用 Eclipse 常用的大綱視圖。GMF 自動生成的代碼對於大綱視圖的支持比較弱,在圖元嵌套的情況下,不能完整顯示模型的結構,因此我們需要擴展開發支持多層模型的大綱視圖。





開發大綱視圖

在自動生成的插件項目中,有代表編輯器的 XXXEditor 類,它在 plugin.xml 中註冊為 org.eclipse.ui.editors 擴展點元素。Editor 類中有繼承得到的 getOutlineViewEditPartFactory 方法,它返回 EditPartFactory 對象,用於創建作為 Outline Tree 節點元素的 EditPart 對象。根據前面的介紹,模型圖上的元素也是 EditPart 對象,它和 Outline 中的 EditPart 對象相對應。GMF 中的 EditPart 在 MVC 架構中承擔 Controller 的作用,它將 Model——EMF 業務模型和 View——Draw2D 圖形連接在一起。而編輯器中的 EditPart 和 Outline 中的 EditPart 正是同一個 Model 的不同 Controller,從而呈現出不同的 View。因此,為了正確顯示模型層次,需要重定義 getOutlineViewEditPartFactory 方法。

分析元模型可知,從模型層次角度來分,有三類元素,即頂層的圖元素、中間層的容器元素以及底層的葉節點元素,它們在處理層次關係時體現不同特點。由此,分別定義 DiagramTreeEditPart、ContainerTreeEditPart 和 LeafTreeEditPart,有如下的方法實現。

public EditPartFactory getOutlineViewEditPartFactory() {       return new EditPartFactory() {            public EditPart createEditPart(EditPart context, Object model) {                 if (model instanceof Diagram) {                      return new DiagramTreeEditPart(model);                 } else if (model instanceof Node) {                      EObject objInModel = ((Node) model).getElement();                      if (objInModel instanceof HierarchyDemo.Container)                           return new ContainerTreeEditPart(model);                      else                           return new LeafTreeEditPart(model);                 } else                      return null;           }       };  }  

下面該定義這三個 TreeEditPart 了。一般的 TreeEditPart 中有兩個主要方法,getText() 返回顯示在樹中的節點名稱,本例直接使用模型中的 name 屬性;getImage() 則返回節點圖標。作為容器的 DiagramTreeEditPart 和 ContainerTreeEditPart 還需要實現 getModelChildren() 方法,它用於返回子節點列表,是建立層次關係的關鍵方法。由於前面的開發分別採用了帶標籤的容器和套件兩種方法,它們在表達層次關係時略有不同,因此 getModelChildren 也有不同的實現方式。

大綱視圖——帶標籤的容器

在元模型中,Diagram 包含 Node,所以 DiagramTreeEditPart 的 getModelChildren() 可以返回模型的子元素,即 model.getChildren()。帶標籤的容器中,第一個子元素就是標籤,因此實際包含的圖元是除了標籤之外的其它子元素。

//ContainerTreeEditPart  protected List getModelChildren() {       if(getModel() instanceof View){            //remove the first label            EList children = ((View)getModel()).getChildren();            List realChildren = new ArrayList(children.size()-1);            for(int i=0;i<children.size()-1;i++){                 realChildren.add(children.get(i+1));            }            return realChildren;       }       return null;  }  

大綱視圖——套件

由於套件作為容器 Container 的子元素,套件中容納的圖元與 Container 本身並不是直接包含的關係。而在顯示層次關係的 Outline 樹結構中,Container 之下應該是邏輯上包含的圖元,這樣 ContainerTreeEditPart 就返回對應 compartment 的子元素列表。但是,這並不正確,因為缺少了 Compartment 這一層,沒有接收對應事件的主體,在其中進行的編輯操作(如添加子節點)無法及時體現在樹中。可以換一個角度,由於 Compartment 與所依賴的 Container 對應同樣的模型元素,outline 樹結構中的容器節點就可以對應於 Compartment,這樣既可以顯示模型信息,又能及時反映層次的變化。DiagramTreeEditPart 和 ContainerTreeEditPart 具有相同的 getModelChildren 方法。

//DiagramTreeEditPart & ContainerTreeEditPart  protected List getModelChildren() {       ArrayList resultList =new ArrayList();       if(getModel() instanceof View){            List initList=((View)getModel()).getChildren();            for(int i=0;i<initList.size();i++){                 View item=(View)initList.get(i);                 if(item.getType().equals(                   new Integer(ContainerEditPart.VISUAL_ID).toString())                   || item.getType().equals(                     new Integer(Container2EditPart.VISUAL_ID).toString())){                      resultList.add(item.getChildren().get(1));                 }else                      resultList.add(item);            }       }       return resultList;  }  





小結

GMF 技術將 EMF 與 GEF 連接起來,為快速開發 Eclipse 圖形插件提供了有力支持。本文針對實際情況中帶有層次關係的模型,介紹了如何使用 GMF 開發支持嵌套圖元的編輯器。其中分為兩個方法,一個是帶標籤的容器,另一個是套件。它們都具有容器功能,可以容納其他圖元,但在開發和使用上存在一些差異,現比較如下。

  1. 套件是 GMF1.0 版本就支持的概念,而帶標籤的容器是 GMF2.0 版本新增加的。如果開發環境僅要求 GMF1.0,那麼只能選擇套件了。
  2. 從開發過程來看,套件作為容器圖元的子元素,在圖形定義和映射定義中都需要考慮這一特殊元素的處理;而帶標籤的容器本身就是容器圖元,可以更直接的表達層次關係。
  3. 套件可以摺疊,以隱藏內部包含的圖元及其與外部圖元的連接,而帶標籤的容器無此功能。

GMF 技術也在不斷發展中,希望經過我們的共同努力,它成為我們開發圖形界面的有效工具。(責任編輯:A6)






[火星人 ] 使用 GMF 快速開發支持嵌套圖元的編輯器已經有509次圍觀

http://coctec.com/docs/linux/show-post-69106.html