Graphical Modeling Framework简介
** 本文已发表在2006年12月《程序员》杂志,请勿转载。**
本文假设读者有Eclipse平台应用程序开发经验,了解Eclipse平台的插件机制,使用EMF和GEF开发过应用程序。在本文中,“Eclipse应用程序”等价于“Eclipse插件”。
Eclipse是一个开源的平台,从一开始就被设计为允许各种功能以插件(Plug-in)的形式自由组装的结构,它凝聚了无数天才的设计,目前仅Eclipse.org下的子项目就有数十种,全世界范围内的各种插件则数以千计。随着Eclipse开源社区影响力的不断增强,国内外有越来越多的实际应用构造在Eclipse平台上,同时有更多组织和个人加入到为Eclipse贡献力量的行列中,这样的良性循环形成了以Eclipse平台为中心的巨大生态系统。
在众多插件中,一些插件存在的目标是为了简化开发人员开发其他Eclipse插件的过程,接下来要介绍的GMF就是这样一个插件。
一、GMF与EMF、GEF
开发过Eclipse插件的朋友们一定知道EMF和GEF这两个插件项目。EMF (Eclipse Modeling Framework)可以帮助我们以模型驱动的方式开发Eclipse应用程序,具体来说,它定义了一套名为Ecore的元模型(类似UML元模型,包括包、类型、属性和方法等元素),用Ecore定义的模型可以被EMF转换为运行在Eclipse平台上的Java代码,这些代码实现了一套消息通知机制,是构造一个可靠应用不可缺少的基础。
绝大多数应用程序都有一个业务模型,不论以什么方式描述,这个模型都是整个应用程序的基础。利用EMF强大而完善的建模能力,我们不仅可以更直观方便的构造业务模型,更重要的是,得益于EMF的代码生成能力,在整个应用程序的开发周期里,这个模型都能够保持最新的版本,开发人员在任何时候都能很快的了解整个业务。
虽然对开发人员来说,业务逻辑是应用程序的关键,但直接和用户打交道的还是程序的UI部分。我们经常说一幅图胜过千言万语,这句话在应用程序的UI设计里也同样适用。试想如果我们熟悉的UML类图是用文本来描述的,我们将多花费多少时间在理清类与类之间的关系上呢。GEF的出现,为我们在Eclipse平台下开发图形化的应用程序铺平了道路。
GEF(Graphical Editing Framework)框架具有标准的MVC(Model-View-Control)结构。在模型部分,GEF没有对开发人员进行任何限制,只要模型发生改变时能够通知到控制器即可。GEF里的控制器被称为EditPart,它们是应用程序的核心部分,负责将模型的改变反映到视图,并将用户在视图上所做的操作应用到模型。在视图方面,GEF使用Draw2D,一个轻量级的图形系统。有了GEF的帮助,要在Eclipse里开发出类似Visio的图形化应用程序变得简单多了。
随着Eclipse平台被更多开发人员接受,结合EMF和GEF逐渐成为了构造GEF应用程序的主要模式,也就是在EMF生成的模型基础上开发GEF应用程序,这样可以在模型部分节约不少的工作量。但也有一些问题,例如两个框架各实现了一套命令机制来操作模型等等,它们又在一定程度上削弱了结合这两种技术带来的改善程度。
GMF(Graphical Modeling Framework)让我们可以用更简单的方法实现以前要同时用EMF和GEF开发的应用程序,而结合二者所带来的的各种问题则不需要关心。同时,借助Eclipse的插件机制,GMF还提供了十分丰富的扩展性,便于开发适合特定需求的应用程序。
图表 1 GMF的运行机制
我们不妨拿房子做比喻来说明他们在构造Eclipse应用程序中的地位。一个完整的图形化应用程序就像一个漂亮舒适的家,用EMF构造了业务模型后就相当于买到了毛坯房,这时的房子甚至连门都不全,看起来就像EMF为我们生成的缺省编辑器,十分的简陋。所以接下来的工作就是装修,也就是让应用程序的界面更加漂亮。装修的风格因人而异,这项花费有时甚至比房子本身的价值更高,在装修过程中还有可能对房子本身做出一些修改,例如拆掉一面墙或是做一个楼梯等等,可以把GEF的部分比做对客厅的装修,因为客人来参观时对整个房子的第一印象往往来自客厅,所以在客厅上多花些精力十分有必要,GEF帮我们实现的图形化编辑器就是很大的亮点。然而,不可否认房子的主人每天最多时间是在卧室度过的,所以卧室的布置也是决定房子是否舒适的重要因素,可以把应用程序里各种视图、向导、偏好、菜单和工具条等等比作对卧室的装饰。
至于GMF呢,用它构造图形化应用程序,就像买了一套“精装修”的现房,按开发商的承诺您是可以立即入住的,不仅墙壁喷涂了,地板铺好了,就连整套家具和家电也预备齐全。然而开发商给所有人的装修都是差不多的,顶多提供几套可选方案,不可能完全满足你的全部个性化需求。同样,用GMF生成的程序虽然比EMF生成的好看很多,但还有不少工作是需要自己动手的。
下面,为了能让大家对GMF能为我们做什么有一个直观的印象,我们简单演示一下如何用GMF开发一个流程编辑器。
二、用GMF实现图形化流程编辑器
GMF是以EMF为建模基础的,理所当然业务模型也是一个ecore模型。开始一个GMF项目的第一步就是新建一个空白的EMF项目,在项目里定义我们的业务模型,这个ecore模型里,我们定义一个流程(Process)需要的各种元素。为了节约篇幅,这里的流程模型元素十分简单,只包括开始节点(Beginning)、结束节点(Ending)、流程(Process)、活动(Activity)、条件(Condition)和关系连接(Relationship)这几种类型。其中,Process是根节点,即一个流程文件里只包含一个Process元素。Process下包含多个Activity、多个Condition和多个Relationship,但只包含一个Beginning和一个Ending。
每个流程元素都具有一个字符串类型的名字属性,我们为Activity添加一个优先级(priority)属性用来表示活动的紧急程度,这个属性是枚举类型,具有High、Normal和Low这三个值,在ecore里定义枚举类型是十分方便的;再为Condition添加一个字符串类型的表达式(expression)属性。
图表 2 流程编辑器的ecore模型
现在从ecore模型生成genmodel模型(菜单File -> New -> EMF Model),并生成全部代码(在genmodel模型根节点的右键菜单里选Generate All命令),这个genmodel模型将被后面需要定义的一些模型引用,模型和.edit部分的代码是GMF图形项目编译的前提条件,所以这一步是必要的。
到现在为止的这些步骤和构造普通的EMF应用程序并没有什么不同,不要着急,下面要做的事情就进入GMF的领域了。
现在我们来定义图形模型(Graph Model)、工具模型(Tooling Model)和映射模型(Mapping Model)这三个模型,然后利用它们生成名为GMF生成模型的模型(GMFGen Model,相当于EMF里的Gen Model),最后由生成代码。为了把将要建立的几个GMF模型与已经定义好的EMF模型分开,我们可以新建一个Eclipse项目存放它们,以便在需要时更快的找到想要的模型。
首先来定义图形模型,顾名思义,在这个模型里我们要定义出流程编辑器里每种元素的外观,例如,用圆圈表示开始和结束节点,用方块表示Activity,用菱形表示Condition等等。GMF提供了名为“GMFGraph Simple Model”的向导可以从刚才定义好的ecore文件得到一个缺省的图形模型,我们只要在向导里指定ecore文件所在的位置即可。
接下来定义工具模型,这个模型指定流程编辑器的调色板(Palette)里都可以有哪些工具,你还可以在这个模型里对这些工具进行分组。同样,使用“GMFTool Simple Model”向导可以很快的得到这个模型。
然后是映射模型,这个模型的作用是把ecore模型、图形模型和工具模型联系起来,这样每个业务元素就可以对应到一个外观和一个工具,并且ecore模型里各元素之间的包含、引用关系也可以通过这个模型映射到图元的包含或连线方式。GMF提供的“Guide GMFMap Creation”向导可以帮助我们建立这个模型的一部分内容,其余部分还是需要在编辑器里手动添加或修改。尽管现在这几个模型的编辑界面还显得十分简陋,但在下一个版本(2.0)的GMF里将会提供更多新的GUI以帮助编辑这几个模型,例如提供可视化的模型编辑器。
有了映射模型,我们可以像在EMF里从ecore模型生成genmodel模型那样生成一个gmfgen模型了。生成步骤很简单,只要在Package Explorer里的gmfmap文件上点右键,选择“Create generator model”命令即可。生成以后,我们还可以对gmfgen模型进行微调,因为其中有些信息是在前三个模型里都没有包含的,大部分时候我们只需要根据需要修改其中的少数几个就够了。常用的一个是可以修改Gen Compartment的List layout属性来控制子图形是否可以在父图形里随意拖动。
最后,在gmfgen文件上点右键,选择“Generate diagram code”命令,GMF就会为我们生成完整的图形编辑器代码了。这些代码会存放在名为your.package.diagram的新工程里,这个工程依赖EMF帮你生成的两个工程,所以前面要先生成EMF的工程,否则这个工程无法正确编译。怎么样,这个界面和EMF生成的编辑器比起来够不够“精装修”的标准:
图表 3-1 GMF生成的流程编辑器
图表 3-2 EMF缺省编辑器
当然,这个编辑器的外观可能还不能让你满意,比如除了连接线外所有的图元都是用矩形表示的,未免过于单调,而且每个图元上只显示了有限的文字信息。下面我们介绍一下如何通过修改GMF的几个模型来定制流程编辑器。
首先,要改变各个图形元素的缺省外观,在缺省情况下,GMF建模向导里都将节点元素映射为矩形(Rectangle),连接元素映射为折线(Polyline),当然这些都可以在gmfgraph里修改,例如我们可以让开始和结束节点显示为圆点,让条件节点显示为菱形,将活动节点填充为蓝色,同时可以为连接线的目标端增加箭头图形来表示连接的方向,这样一来,熟悉UML的用户就更容易理解流程图的含义了。
从上面不难看出,gmfgraph模型的作用是定义图形表现的方式,GMF映射模型负责关联业务模型、gmfgraph模型和gmftool模型。任何在gmfgraph里定义的元素如果没有映射到业务模型也是没有意义的,相对来讲gmfmap模型是这三个模型中最复杂的一个,因为几乎每个元素都至少有三个属性需要定义。通过修改gmfmap模型,我们可以规定图形编辑器里,各图形元素的包含关系。在流程编辑器里,可以通过添加一个标签映射(Label Mapping)元素让活动节点里显示优先级属性。
图表 4 定制后的流程编辑器界面
需要注意的是,在每次修改任意一个模型文件后,都要重新生成gmfgen文件,这样重新生成的代码才能反映最近的更新。
三、GMF Runtime介绍
前面已经说过,GMF的Runtime部分是GMF的核心,不依赖GMF帮助生成代码,你也完全可以利用Runtime提供的功能写出漂亮的图形编辑器应用程序,但前提是你对GMF Runtime有充分的了解。
关于GMF Runtime提供了哪些实用功能,在Eclipse.org上有一篇文章“Introducing the GMF Runtime”已经介绍得很详细了,其中主要包括可展开折叠的compartment,Direct-Editing时的快速协助(Quick Assistant)、Pop-up工具条、连接手柄、通用工具项(如备注、文本、各种形状等等)、通用菜单命令和工具条(如颜色、字体、选择、排列、缩放,等等)、动态展示的缩放和自动布局功能、通用属性页、打印和打印预览支持、多种图片格式输出、系统剪贴板支持,等等。
GMF的Runtime相当于对GEF进行了一层包装,如果不想利用GMF生成代码的能力,只使用GMF Runtime也可以开发基于GMF的应用程序。与GEF相比,GMF Runtime充分利用了Eclipse的扩展功能,它提供了十分丰富的扩展点,不仅开发起来很方便(因为各个功能都通过定义的扩展点划分清楚了),同时还便于以后对应用程序的扩展。GMF提供的Logic例子就没有使用代码生成,而是直接写成的,你对GMF Runtime的使用方法如果不清楚,这个例子是很好的参考。
相比起EMF来,GMF项目成立的时间要晚很多,如果想让现有的GEF应用程序使用GMF Runtime带来的新功能,最简单的方法就是在原先代码的基础之上进行修改,而不是从头开始以生成代码的方式进行开发。
和EMF类似,GMF也有代码生成的功能。除了上面提到的情况以外,最好利用GMF的代码生成功能先得到应用程序的框架,再在此基础之上进行开发。
在GEF或EMF+GEF构造的应用程序里,一个让人头疼的问题就是在业务模型里不得不包含一些与图形有关的信息,例如每个元素在编辑器里的位置、大小、颜色、字体等等,这可能让本来清晰的业务模型变得难以理解。GMF通过引入记号模型(Notation Model)解决了这个问题。在GMF里,EditPart不再与业务模型直接相连,而是连接到记号模型(GMF的EditPart#getModel()方法得到的是记号模型),由记号模型维护到业务模型的引用。这个记号模型是GMF预先定义好的,它把图形抽象为画布、节点和连接三大类元素,开发人员只要知道GMF的这个设计就可以了,而不需要在业务模型里做任何特殊操作。GMF在持久化一个业务模型实例的时候(即用户保存图形编辑器时),缺省会得到两个文件,一个是业务模型的持久化信息,另一个是记号模型的持久化信息,后者依赖前者的内容。当然,用户也可以在gmfgen里设定参数让GMF只生成一个文件包含这两者的全部信息,但就失去了业务与图形分离的好处。
前面说过,GMF应用程序里的很多功能都是通过扩展点实现的,GMF里这些扩展点被称为服务(Services),用户通过在插件里实现这些服务可以改变图形编辑器的行为。这些服务包括控制控制调色板(Palette)上有哪些工具,业务模型与EditPart的对应关系,EditPart上的EditPolicy等等。实现GMF服务的方法和实现其他Eclipse平台扩展点的方法是一样的,用户要实现的接口名称一般以Provider结尾,例如EditPartProvider、ViewProvider和PaletteProvider,等等。
四、总结
GMF不仅在名称上集合了EMF和GEF这两个框架,它同时拥有EMF的代码生成能力和GEF的图形编辑能力。以往对EMF程序的定制主要通过修改代码实现,在GMF里,我们更多通过实现各种扩展点来改变应用程序的外观和行为,当然,必要的时候修改代码也是不可避免的。
另外,GMF还利用了EMFT项目里的不少成果,例如EMFT Transaction、EMFT Workspace和EMFT OCL等等,EMFT项目是对EMF项目的有益补充,它们让GMF应用程序更加健壮,更好的和Eclipse平台集成以及更好的用户体验,但也在一定程度上增加了GMF本身的入门门槛。
本文发出时,GMF 1.0版本已经相对比较稳定,而GMF 2.0也有了Milestone2版本。如果是做实际项目推荐选择1.0版本,因为虽然这两个版本的模型略有区别,但2.0正式版按计划要在2007年四月前后才能发出,这中间API很可能还会有多次修改。GMF 2.0的建模用户界面更友好一些,至于Runtime增加的新功能我们拭目以待。