最新消息:热烈庆祝IT小记上线!

GMF入门资料超全

最近发现一个比较全的GMF资料分享一下!

  
        上图中紫色的椭圆结点在ecore模型中代表Method,在属性视图中可见它的一些属性。如果要使TimeProperty属性值为"NONE" 时,TimeUnit和Value值分别变为"NONE"和0.0,应该如下修改MethodItemProvider类中的 notifyChanged()方法(紫色的代码是添加的,其他均为GMF生成的):

    public void notifyChanged(Notification notification) {
        updateChildren(notification);

        switch (notification.getFeatureID(Method.class)) {
            case EventPackage.METHOD__PROPERTY_NAME:
         {
                //如果该项设置为NONE,那么TimeUnit和Value项应该分别设置为NONE和0.0
                Method owner = (Method)notification.getNotifier();
                TimeProperty newValue = (TimeProperty)notification.getNewValue();
                if(newValue.equals(TimeProperty.NONE))
              {
                    owner.setTimeUnit(TimeUnit.NONE);
                    owner.setValue(0.0);            
                }
             }
            case EventPackage.METHOD__VALUE:
            case EventPackage.METHOD__TIME_UNIT:
            case EventPackage.METHOD__CLASS_NAME:
            case EventPackage.METHOD__METHOD_NAME:
            case EventPackage.METHOD__PARAMETERS:
            case EventPackage.METHOD__RETURN_TYPE:
                fireNotifyChanged(new ViewerNotification(notification, notification.getNotifier(), false, true));
                return;
        }
        super.notifyChanged(notification);
    }

  MethodItemProvider类位于genmodel生成的edit项目中,它相当于Property View的ContentProvider。

  • 17:11
  • 评论 / 浏览 (0 / 610)
2008-07-09

GMF:基于Reference的连接

    博客分类:
  • GMF

GMF中的Link有两个,分别是基于Type的和基于Reference的。对于前者,在ecore模型中有一个类对应这个连接,创建一个连接即创建了该类的一个实例,试想,如果一个模型比较复杂,有很多连接时,这种方法要在模型中要为连接建立很多与业务无关的类,使得模型非常复杂;对于后者,没有必要为每个连接在ecore中建立一个类,而是设置该连接对应的Reference即可。下面用一个school的模型来说明基于Reference的连接的建立方法,这个模型非常简单,如下图(用gmf插件生成的对应school.ecore的school.ecore_diagram):一个学校里边有很多老师和学生,每个老师会指导多个学生,这里的指导关系是Teacher类的一个Reference,名字为tutoringStudents,我们打算在GMF编辑器中用Node来表示老师和学生,老师对学生的指导关系用Link来表示。

    该模型命名为school.ecore, 然后生成school.genmodel,再按照常规方法建立school.gmfgraph, school.gmftool.
    最后建立最关键的school.gmfmap,首先为mapping元素建立两个Top Node Reference,分别对应Teacher和School,然后建立代表老师指导学生关系的link mapping,其属性页中各项按照下图指定值:
   
    注意,Domain meta information中前三项都空着,只在最后一项中指定代表该连接的Reference即可。Validate无错后,生成school.gmfgen,在生成Diagram code,最后运行效果图如下:

      看一下保存semantic model的school文件: 

<?xml version="1.0" encoding="UTF-8"?>
<School:School xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:School="http://example.School">
  <teachers name="ProfessorLi" tutoringStudents="//@students.0 //@students.1"/>
  <students name="Tom"/>
  <students name="Mary"/>
</School:School>
  • 17:10
  • 评论 / 浏览 (0 / 375)

要让GMF编辑器中的图元更加生动,可以通过在gmfgraph中增加一些元素(比如前景色)来定制图元的显示形式,但是有些特征(比如字体)无法仅仅通过gmfgraph来完成,这就需要我们修改GMF生成的Diagram Code来达到所需要的效果。如下图所示:
 
   1.前景色:
      图中紫色的椭圆代表Method类型的元素,起前景色可以通过gmfgraph来定制:
     
    2.线条宽度(2),线条种类(LINE_SOLID):
    
    也可以通过修改MethodEditPart中的内部类MethodFigure的构造函数来完成1和2两个特征的定制:

public MethodFigure() {
    this.setFill(true);
    this.setFillXOR(false);
    this.setOutline(true);
    this.setOutlineXOR(false);
    this.setLineWidth(2);
    this.setLineStyle(Graphics.LINE_SOLID);
    this.setForegroundColor(METHODFIGURE_FORE);
    createContents();
}


   3.字体: 要通过修改MethodNameEditPart的setLabelTextHelper方法来完成。
    

    protected void setLabelTextHelper(IFigure figure, String text) {
        if (figure instanceof WrapLabel) {
            ((WrapLabel) figure).setText(text);
            
            FontData fd = new FontData();
            fd.setStyle(SWT.BOLD);
            ((WrapLabel) figure).setFont(new Font(null, fd));

        } else {
            ((Label) figure).setText(text);
        }
    }

  
     4.结点默认大小:由于图中两个黄色的“BEGINING"和"ENDING"结点中的文字不需要修改,因此这两个结点的默认大小可以根据字符串的宽度和高度来设置(GMF默认的高度和宽度是40,40)。修改BeginingEditPart中的createNodePlate方法(注:由于编辑器中的结点Shape是放在Plate上,Plate再放在画布上的,因此Plate的大小决定了编辑器中图元的大小,修改Shape没用)。

    protected NodeFigure createNodePlate() {
        DefaultSizeNodeFigure result = new DefaultSizeNodeFigure(getMapMode()
                .DPtoLP(EventConstant.BEGINING_DEFAULT_WIDTH), getMapMode().DPtoLP(EventConstant.BEGINING_DEFAULT_HEIGHT));
        return result;
    }
  • 17:09
  • 评论 / 浏览 (1 / 540)
** 本文已发表在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增加的新功能我们拭目以待。

  • 11:16
  • 评论 / 浏览 (1 / 709)

假设GMF为你生成的项目名称为com.example.diagram,现在要在右键菜单里增加一个自定义命令,并关联在名为Activity的模型元素上,即只有在Activity类型的元素上点右键,弹出菜单里才有这个自定义命令。此命令的功能是简单的把该Activity的Name属性改为“Modified Activity”。实现的步骤如下:

1、如果之前没有创建过,则创建一个名为com.example.diagram.custom的plugin项目(以下简称为“custom项目”),新建这个项目的目的是把自己的定制与GMF生成的代码分开;

2、在custom项目里实现org.eclipse.ui.popupMenus扩展点,这样会在右键菜单里多出一个"Change"菜单项,下面有"Name"命令;

 
<extension
         point="org.eclipse.ui.popupMenus">
  <objectContribution
        adaptable="false"
        id="com.example.custom.objectContribution.ActivityEditPart"
        objectClass="com.example.diagram.edit.parts.ActivityEditPart">
     <menu
           id="BMAChange"
           label="&amp;Change"
           path="additions">
        <separator name="group1"/>
     </menu>
     <action
           class="com.example.diagram.popup.ChangeActivityNameAction"
           enablesFor="1"
           id="com.example.diagram.popup.ChangeActivityNameAction"
           label="&amp;Name"
           menubarPath="BMAChange/group1"/>
  </objectContribution>
</extension>
 

3、实现上一步里定义的Action类ChangeActivityNameAction,这个类不仅要实现 IObjectActionDelegate(popupMenus扩展点的要求),还要继承自AbstractActionDelegate这个类(GMF的要求)。我们要做的是实现doRun()方法,首先取得当前选中的editpart,然后创建一个SetRequest实例,它包含了改变属性操作的所有信息,包括目标对象、属性的名字和新属性值。因为GMF里editpart的getModel()方法不是业务模型里的元素了,而是View对象,要再调用View#getElement()才能得到业务模型里的元素,所以代码里我们利用 ViewUtil#resolveSemanticElement()方法直接得到Activity对象。另外,GMF使用了EMFT的 Transaction项目来操作模型,所以editpart.getEditingDomain()方法得到的会是一个 TransactionalEditingDomain类型。

有了request,我们用它作为构造参数创建一个SetValueCommand(),这是一个GMF命令(实现 org.eclipse.gmf.runtime.common.core.command.ICommand),用来改变属性值。最后要执行这个命令,我们知道command是要用CommandStack来执行的,这样才能undo/redo,但 editpart.getDiagramEditDomain().getDiagramCommandStack()得到的CommandStack只能执行GEF的命令(org.eclipse.gef.commands.Command),所以要把我们的command用 ICommandProxy()包装一下,这样就没问题了。

public class ChangeActivityNameAction extends AbstractActionDelegate 
                implements IObjectActionDelegate {

    protected void doRun(IProgressMonitor progressMonitor) {

        // Get the selected edit part
        IStructuredSelection structuredSelection = getStructuredSelection();
        Object selection = structuredSelection.getFirstElement();
        IGraphicalEditPart editpart = (IGraphicalEditPart) selection;

        // Create a command to modify its property
        SetRequest request = new SetRequest(
                editpart.getEditingDomain(),
                ViewUtil.resolveSemanticElement((View) editpart.getModel()),//The semantic model 
                BmaPackage.Literals.ACTIVITY__NAME,//Name feature of activity
                "Modified Activity");//New name value
        SetValueCommand command = new SetValueCommand(request);

        //Do the work
        editpart.getDiagramEditDomain().getDiagramCommandStack().execute(new ICommandProxy(command));

    }
}

Update: 可以用IGraphicalEditPart#resolveSemanticElement()直接取得editpart对应的 EObject,IGraphicalEditPart#getNotationView()是得到View对象,和getModel()作用一样。

运行效果如下,选择修改名字命令后,Activity1的名字改为Modified Activity,并且可以undo/redo:


参考:

  • GMF提供的Logic例子中CreateLogicElementActionDelegate.java文件
  • GMF Tips,Change Names Of Newly Created Elements小节
  • GMF Tutorial Part 3

Update(2007/07/17):似乎GMF更推荐使用IOperationHistory来修改模型,例如在Action的doRun()方法里像下面这样写:

AbstractTransactionalCommand command = new AbstractTransactionalCommand(
        editingDomain,
        "Modifying the model", Collections.EMPTY_LIST) { 
    protected CommandResult doExecuteWithResult(
            IProgressMonitor monitor, IAdaptable info)
            throws ExecutionException {
        //在这里修改模型
        return CommandResult.newOKCommandResult();
    }
};
try {
    OperationHistoryFactory.getOperationHistory().execute(command,
            new SubProgressMonitor(progressMonitor, 1), null);
} catch (ExecutionException e) {
    MindmapDiagramEditorPlugin.getInstance().logError(
            "Unable to create model and diagram", e); //$NON-NLS-1$
}

因为在GMF新闻组里提到过:"in a GMF application, you should probably never execute commands in a CommandStack, because it will not be worth the effort of coordinating the IUndoContexts applied to these commands and your GMF AbstractTransactionalCommands to ensure that the Undo/Redo menus make sense.",原文链接

  • 17:01
  • 评论 / 浏览 (0 / 821)

2006 年 11 月 27 日

本文介绍了 Graphical Modeling Framework(GMF)项目,说明了如何开发一个简单的 Eclipse Modeling Framework(EMF)模型,并使用 GMF 的工具将其转换成典型的图形化编辑器。

背景

坦白说:过去在 Eclipse 里使用 Graphical Editor Framework(GEF)创建图形化编辑器 既慢又痛苦。这个过程包括理解复杂的框架和大量的冗余代码。但也说明 GEF 是创建图形化编辑器的最佳框架,因为它与模型无关。另一方面,与模型无关本身也有一些问题。

GMF 杂谈

GMF 的运行时组件是 IBM® 为使用 Eclipse Foundation 开发的,它以前还受 IBM Rational® 建模产品的支持。

GEF 是 Model-View-Controller(MVC)机制的精髓,它允许将您自己的模型引入表中。在使用 GEF 的早期,大多数人还使用自定义模型(考虑传统 Java 对象 [Plain Old Java™ Object, POJO])。您会发现自定义模型带来的问题是需要自己编写通用代码来支持模型,如序列化及侦听模型更改的功能。

在 GEF 中使用模型的下一个逻辑步骤是要使用 Eclipse Modeling Framework(EMF),EMF 提供了以各种形式将模型序列化的工具和侦听对模型默认值的更改的功能。

想要了解 EMF 的更多信息?

如果您觉得缺乏 Eclipse Modeling Framework(EMF)方面的基础知识,或想要强化关于 EMF 的一般知识,建议您看书或者 developerWorks 中的一系列介绍性文章,这些都是非常好的参考资料。有关这些参考资料和其他 EMF 参考资料,请参阅 参考资料

但是,将 EMF 模型与 GEF 框架整合在一起有一定的技术难度(如不同的命令堆栈),导致 EMF 模型并没有很快被基于 GEF 的编辑器接受。最终,GMF 项目在这种逆境中应运而生,并希望能够引入一种快速生成图形化编辑器的方法。通过类似的方法,EMF 生成适用于 EMF 模型的基本编辑器。

创建 EMF 模型

创建模型的第一步就是定义 EMF 模型的协作对象。我写这篇文章的目的只是说明定义模型的过程,而不是深入讲解 EMF 提供的模型处理工具的用法。本例中将要使用的模型是一个简单的图形化模型。我将通过一张图来帮助我说明模型的外观。


图 1. 可视的图形化模型
可视的图形化模型

如您所见,使用模型是帮助我们理解各部分之间关系的一种简单方法。这个模型由图形、连接和图形化图表组成。

EMF 支持通过多种方法定义模型。为简单起见,我决定使用加注的 Java 技术。下面的代码清单说明了如何使用 EMF 定义模型。第一个模型对象是一个有名称属性、源连接和目标连接(Connection 类型)的图形。请注意,这是一个抽象的 EMF 类。


清单 1. Shape.java

/** * @model abstract="true" */public interface Shape {/** * @model */String getName();/** * @model type="com.ibm.model.shapes.model.Connection" containment="true" */List getSourceConnections();/** * @model type="com.ibm.model.shapes.model.Connection" */List getTargetConnections();} 

 

接下来定义囊括所有图形的列表的图形化图表。


清单 2. ShapesDiagram.java

  /** * @model */public interface ShapesDiagram {/** * @model type="com.ibm.model.shapes.model.Shape" containment="true" */List getShapes();}

 

接下来定义一些特殊的图形使模型更加生动。


清单 3. RectangularShape.java

  /** * @model */public interface RectangularShape extends Shape {}



清单 4. EllipticalShape.java

  /** * @model */public interface EllipticalShape extends Shape {}

 

最后,再添上连接就可以了,这样就能将各种图形真正连接在一起。


清单 5. Connection.java

  /**  * @model  */public interface Connection {/** @model */Shape getSource();/** @model */Shape getTarget();}

 

使用 Java 编程语言定义了模型后,请单击 File > New > Eclipse Modeling Framework > EMF Model(参见图 2)定义一个新的 EMF genmodel。注意:如果还没有 EMF 项目,就先创建一个。


图 2. EMF 加注的 Java importer
EMF 加注的 Java importer

生成 EMF 模型

如果在生成 EMF 模型遇到问题,请参阅名为 "Generating an EMF Model" 的教程,它可以帮助您入门。请参阅 参考资料

创建了 EMF genmodel 后,请在文件上单击鼠标右键,并确保生成 ModelEdit 组件(您只需选择 Generate All 就可以轻松地完成)。

创建 GMF 模型

GMF 需要您先创建一组模型,然后生成图形化编辑器。图 3 显示了创建这些模型所涉及的过程。我们需要使用的第一个模型是图形化定义,它定义了编辑器生成后的视觉效果。接下来是工具定义,它包括与编辑器面板、菜单等相关的事务。最后,还需要一个模型就是映射定义,相信您猜得到,它用于定义业务逻辑(EMF 图形化模型)与可视化模型(图形化和工具定义)之间的映射。


图 3. GMF 概览(来自 GMF 维基)
GMF 概览

GMF 有个叫 GMF 指示板(Window > Show View > Other > GMF Dashboard)的实用程序非常好。它可以帮助您用一种简单的方法完成图形化编辑器生成过程。在这个阶段,您必须选定域模型和域 genmodel。


图 4. GMF 指示板
GMF 指示板

GMF 图形化定义模型

GMF 备忘单

GMF 有一个优秀的备忘单,它可以帮助您完成用 GMF 生成编辑器的创建过程。建议将此备忘单与 GMF 指示板结合使用。请通过菜单项 Help > Cheat Sheets... 访问备忘单。

需要创建的第一个模型是图形化定义模型(在指示板的 Graphical Def Model 下单击创建超级链接)。确保选择 Canvas 为模型对象。这个模型很容易创建:

  1. 创建图表中显示的图。方法是在编辑器中创建一个新的 Figure Gallery 条目,然后创建各种图。
  2. 创建图表中显示的节点(矩形和椭圆形)。
  3. 在图表中创建连接。
  4. 确保每个节点都与图库中创建的图相匹配。

注意:如果您在执行此任务时遇到问题,您可以下载一个样例插件,此插件为您准备好了所有模型。


图 5. GMF 图解模型
GMF 图解模型

GMF 工具定义模型

这个步骤需要定义工具定义模型,使用此模型可以定义图形化编辑器的信息类面板和菜单。要定义工具定义模型,请打开 GMF 指示板,然后单击 Create。我们的简单模型只需定义一个面板和一些创建工具来辅助创建模型(参见图 6)。


图 6. GMF 工具模型
GMF 工具模型

GMF 映射定义模型

所有工作都从映射定义模型 开始。在此模型中,我们将可视化(图形)模型映射到业务逻辑(域模型)中。GMF 有一组有序向导可以帮助您创建映射定义。请通过 File > New > Graphical Modeling Framework > Guide GMFMap Creation 调用这组向导。第一步要选择所有 GMF 模型(参见图 7)。


图 7. GMFMap 向导 1
GMFMap 向导 1

接下来,向导将智能地提示我们选择要使用哪个模型元素作为图表根元素。在我们举的例子中,此模型元素为 ShapesDiagram


图 8. GMFMap 向导 2
GMFMap 向导 2

最后,GMF 会像变魔术一样算出哪些模型元素必须映射到哪些可视化元素上。


图 9. GMFMap 向导 3
GMFMap 向导 3

自定义 GMF 映射

可以使用基本编辑器编辑 GMF 映射定义文件来添加更多高级自定义功能。要勇敢地去尝试!

必须注意的是:这些向导可能会随着 GMF 的发展而改变。据说使用 GMF 自身提供的图形化编辑器就可以帮助创建映射定义文件(和其他 GMF 模型)。

生成 GMF 编辑器

整个过程的最后一步也是最有趣的一步是生成图形化编辑器。这需要您先通过映射定义模型创建一个 GMFGen 模型。然后,在映射定义文件上单击鼠标右键并选择 Create generator model...。这样,一个包含使用图形化编辑器所需的全部代码的新项目就生成了。要使用图形化编辑器,请启动一个新的 Eclipse 运行时工作台,然后转到 File > New > Examples > Model Diagram(参见图 10)。


图 10. 模型向导
模型向导

模型文件创建后,您就应当能够使用生成的编辑器了(参见图 11)。很不错,是吧?


图 11. 图形编辑器
图形编辑器

自定义 GMFGen 模型

通过映射定义文件获得的 GMFGen 模型是可以根据需要加以修改的。例如,GMFGen 模型的属性就可以使用属性视图进行编辑。GMFGen 模型还有可以控制生成代码的命名规则、生成的编辑器是否支持打印功能及很多其他自定义操作的属性。试试这些选项,然后根据您的需求对其进行自定义。

GMF 特点

必须注意的是:我们构造并生成的编辑器仅用到了 GMF 功能的一小部分。您可以做很多改动来利用框架的高级功能。

例如,GMF 支持验证。这句话的含义是:如果我们要限定图形化模型,只许与每个模型元素有一个连接会怎样?只允许相似的元素相互连接,还是对可用于图形的名称类型进行控制?GMF 完全能够支持这些类型的验证,甚至其他验证。对于验证,GMF 利用了 Eclipse Modeling Framework Technology(EMFT)来支持涉及使用 Java 代码和对象约束语言(Object Constraint Language,OCL)来定义验证器的情况。GMF 将给未通过验证的文件安插错误标记,这类似于 Eclipse 对未编译的 Java 文件的处理方法。要查看关于 GMF 支持内容的更多信息,请参阅 参考资料

 



回页首


结束语

我的目的有两个:一是展示支持模型驱动开发的 Eclipse Callisto 发行版中令人兴奋的新的一面;二是让您了解它有多棒,只需 15 分钟就能在 Eclipse 中生成图形化编辑器。

  • 15:07
  • 评论 / 浏览 (1 / 602)

2007 年 2 月 28 日

基于 EMF 和 GEF,Graphical Modeling Framework(GMF) 提供了图形化编辑器的开发环境和运行时框架。本文首先简单地阐述 GMF 框架的基本内容,然后结合具体实例 Zachman View 介绍了如何对 GMF 代码框架进行高级的扩展和定制,以满足复杂图形化编辑器的开发需求。

前言

在 GMF 出现之前我们在做一个基于模型的图形化工具时,通常用 EMF 来创建后台模型, 用 GEF 来做用户界面和后台模型的同步,这需要花费相当的时间去了解 EMF 的模型设计方法和 GEF 的 MVC 框架。

为了简化开发过程,GMF 对 EMF 和 GEF 做了进一步的封装,提供了一套图形化的开发环境和运行时框架,开发人员可在图形环境中轻松定制自己的领域模型,并基于该模型一步一步地进行设计开发。最后 GMF 根据用户的定制情况,自动生成代码框架。

本文以 zachman view 为例子,主要介绍在用 GMF 生成一个 zachman 的 diagram 后,如何对代码作进一步的高级扩展以满足具体的要求,下面将在 5 个方面进行扩展:

1. 自定义布局管理器和背景色

2. 定制上下文菜单

3. 动态加载 Palette Entry

4. 定制 EditPolicy 和 Command

5. 模型转换




回页首



GMF 简介

GMF 框架主要包括两个部分:运行时框架,图形化开发环境。图形开发环境依赖底层的运行时框架。


图 1 GMF 组件
图 1 GMF 组件

如图 1 所示,GMF Editor 主要基于 GMF Runtime 来构造应用框架,但同时允许开发人员直接使用 EMF、GEF 和 Eclipse 系统接口来完成一些特定功能(GMF 不能很好支持的功能)。

GMF 的图形化开发环境

GMF 编辑器的主要功能是用来定义一组用来生成代码框架的模型,主要包括:graphical model, tool model 和 map model。


图 2 GMF 模型
图 2 GMF 模型

如图 2,GMF 编辑器首先需要一个 domain model,通常是一个 *.ecore 文件,GMF 根据这个 domain model 做进一步的操作,这个 model 是实施 GMF 后续工作的基础。

  • graphical model:定义了 ecore model 元素的图形信息,如形状,颜色,边框等等。严格的说这个图形信息是和 ecore model 无关的(以便于领域模型和用户界面模型的映射),但是在具体的操作过程中我们都是根据 ecore model 来生成其所对应的 graphical model.
  • tool model:定义了在未来生成的图形编辑器中 palette view 里面的 palette entry 信息。 如名字,图标。
  • map model:定义了 ecore model, graphical model 和 tool model 三者间的映射关系,通过 map model 来调整其他 3 个 model 的映射关系可以增强 model的可重用性和灵活性。
  • generator model:由 map model 结合其他三个 model 生成,最终 GMF Runtime 根据 generator model 来生成 diagram editor 的代码框架。可以通过调整 generator model 的一些属性来做一些个性化的工作。比如:默认 GMF 框架会把 ecore model 和 notation model(diagram model) 分开来保存,可以通过修改其属性"Same File For Diagram And Model" 来使 model 和 view 保存在一个文件中,等等。




回页首



GMF 进阶

环境:

GMF 可以方便的为开发人员生成模型的 diagram 框架,和一些基本的功能。本文以 zachman view 为例子具体讲述在用 GMF 生成一个 diagram 框架后,如何进一步对代码进行扩展以满足具体的要求。

图 3 是 zachman view 的 ecore model, zachman 由行和列组成,每个列包含一些 CItem 对象,每个行引用一个或多个 CItem 对象,每个 CItem 关联一个特定的行。每个 CItem 包含 0 个或多个属性。


图 3 zachman ecore model
图 3 zachman ecore model

根据图3的 ecore model,我们可以借助 GMF 的图形化开发环境快速地生成与之对应的 graphical model, tool model 和 map model,进而由 generator model 生成 diagram 的基本框架(详细步骤可参考 15 分钟学会 Eclipse GMFGMF Tutorial)。生成的 diagram 如下图 4,


图 4 生成的 Zachman view diagram
图 4 生成的 Zachman view diagram

图4 中,用户必须自己调整每个元素的位置,其中的行和列对象不能像表格一样自动调整布局,Palette Viewer 上的 entry 项也没有办法进行定制,此外,弹出菜单上也有好多的无关项容易引起误会,等等。

期望的 diagram 如下图5


图 5 扩展后的 Zachman view diagram
图 5 扩展后的 Zachman view diagram

在图 5 中,扩展了 Zachman 的布局管理器,行和列将自动调整它们的布局,修改了上下文菜单,去掉了无关的菜单项,同时增加了用户自定义的 palette entry 项等等。下面的章节将对这些实现的细节进行详细说明。

自定义布局管理和背景色

在图 4 中,每个行(列)的位置是由创建对象时鼠标的位置决定的,调整一个列的高度不会引起其他列的高度的同步改变,因此,为了实现行和列的自动按表格布局,行列不重叠,列同高,行同宽,需要对 Zachman view 的布局管理器进行定制。

Zachman ecore model 中根元素 Zachman 和其中的每个元素生成对应的 EditPart,每个 EditPart 中定义了一个 Figure 对象用来表示元素的图形化信息,这个 Figure 的属性和外观特点就是通过在 graphical model 里的描述生成的。每个 Figure 有一个布局管理器,由它来管理其中的子元素的布局位置。通常根元素的所对应的布局管理器是 FreeformLayout,该布局管理器中,用户可随意指定子对象位置大小。下面将解决如何扩展 FreeformLayout 来定制根的子元素的布局。

首先需要定一个布局管理 ZachmanDiagramLayout,


清单 1 自定义布局管理器

public class ZachmanDiagramLayout extends FreeformLayout {  ...   public void layout(IFigure parent) {    Iterator cit = parent.getChildren().iterator();    IFigure f;    ...    while(cit.hasNext()){      f = (IFigure)cit.next();      Rectangle bounds = (Rectangle) getConstraint(f);            //计算出f的bounds            f.setBounds(bounds);      setConstraint(f, bounds);      ...    }  }  ...}


其次,安装 ZachmanDiagramLayout。根 ZachmanEditPart 元素扩展的是 DiagramEditPart,其定义了一个方法 protected IFigure createFigure() {…},由这个方法返回根元素 ZachmanEditPart 的图形对象,只要给它设置 ZachmanDiagramLayout 就可以了。如下


清单 2 安装布局管理器

protected IFigure createFigure() {	IFigure f = super.createFigure();	ZachmanDiagramLayout layout = new ZachmanDiagramLayout();	f.setLayoutManager(layout);		return f;}


如果要定制某些对象的布局,都可以按照上面两个步骤通过给其父对象安装布局管理器来实现。

如果想要修改 ZachmanDiagramEditor 的背景色,可以通过 GMF editor 的 getGraphicalViewer().getControl() 获取 editor 的画布,调用其 setBackgraoundColor(…) 方法就可以给 editor 设置新的背景色了,如果加上调用画布的 redraw() 方法,就可以随需变换 editor 的背景色了。

定制上下文菜单

ZachmanDiagramEditor 自带了很多系统的菜单项,如图6


图 6 上下文菜单
图 6 上下文菜单

在实际的应用中,很多菜单项都是多余的,如何把多余的菜单项去掉并且添加自定义菜单呢?

GMF 提供了一个扩展点 org.eclipse.gmf.runtime.common.ui.services.action.contributionItemProviders。这个扩展点有一个子项 contributionItemProvider,它对应的 class 为 com.ibm.eis.soaif.smm.model.diagram.providers.DiagramContributionItemProvider,由这个 DiagramContributionItemProvider 决定了 GMF Editor 上 contextMenu 的内容,所以要需要重写 ContextMenu 的这个 provider 来定制菜单内容。


清单 3 定制上下文菜单

public class ZachmanContextMenuProvider extends DiagramContextMenuProvider {   public void buildContextMenu(IMenuManager menu) {     ...     // 添加用户自定义菜单项     // ...     super.buildContextMenu(menu);   }// 过滤不需要的上下文菜单   public IContributionItem[] getItems() {     IContributionItem[] ic = super.getItems();     filterSystemPopMenu(ic);     return ic;    }}


通过 filterSystemPopMenu(),过滤掉不需要的系统菜单信息。一个 IContributionItem 对应一个菜单信息,通过设置其为不可见,可以过滤掉相应的菜单项。

添加用户自定义 Action 到上下文菜单,首先定义一个 Action


清单 4 自定义 Action

public class SampleAction extends Action { public SampleAction(String text) {     super(text);     setId("Zachman.sample");     setText("Sample"); } @Overridepublic void run() {    super.run();}}


添加 SampleAction 到上下文菜单中去


清单 5 添加自定义 Action 到上下文菜单

public void buildContextMenu(IMenuManager menu) {   SampleAction action = new SampleAction("Sample");   menu.add(action);   super.buildContextMenu(menu); }


最后,需要用 ZachmanContextMenuProvider 去替换 ZachmanDiagramEditor 原有的 DiagramContextMenuProvider,修改 ZachmanDiagramEditor 的 configureGraphicalViewer() 方法如下:


清单 6 为 Editor 安装 ContextMenuProvider

protected void configureGraphicalViewer() {  super.configureGraphicalViewer();  IDiagramGraphicalViewer viewer = getDiagramGraphicalViewer();  /* customize popup menu */  ContextMenuProvider provider = new ZachmanContextMenuProvider(this,viewer);  viewer.setContextMenu(provider);  getSite().registerContextMenu(ActionIds.DIAGRAM_EDITOR_CONTEXT_MENU	  ,provider	  ,viewer);    }


新的上下文菜单如下图7


图 7 用户自定义菜单
图 7 用户自定义菜单

动态加载 Palette Entry

在图 3 中,CItem 包含多个属性项,根据属性项的变化,我们希望能够在 palette viewer上同步更新 palette viewer 上的 entry 项。

GMF 生成的 Palette View 上提供了用户在 tool model 里面定义的 palette entry, 但是它们是静态的,如何像 palette view 上根据需要动态添加 palette entry 呢?

首先,每个 palette Viewer 如下


图 8 palette Viewer
图 8 palette Viewer

红色区域是一个 PaletteContainer,它里面有 Column, Row 和 CItem 三个 ToolEntry。每个 PaletteViewer 对应一个 PaletteRoot, PaletteRoot 是一个 PaletteContainer,它是 PaletteViewer 的根容器。下面介绍如何向 palette viewer 添加一个 Sample 属性项。

1. 定义 Tool, 由 Tool 完成 ToolEntry 在接收到鼠标事件的具体动作。


清单 7 SampleCreationTool

public class SampleCreationTool extends SelectionTool {protected boolean handleButtonDown(int button)    {        if(button == 3){    	        //TODO right click event    	    }    	    if(button == 1){            //TODO left click event 		    /* set the default palette selection */getDomain().loadDefaultTool();}}


2. 定义 ToolEntry, ToolEntry 是 PaletteViewer 里面可以显示的一个 entry,如图8中的 "Zoom"、"Column" 等。


清单 8 定义 ToolEntry

public class AttibuteCreationToolEntry extends ToolEntry{	public Tool createTool() {		SampleCreationTool tool = new SampleCreationTool();    tool.setUnloadWhenFinished(false);            return tool;	}       }


3. 归类 ToolEntry 项。PaletteDrawer 是对 PaletteContainer 的一个扩展,这里为 CItem 创建一个 Sample 属性来归类属于 Sample 的 ToolEntry

a. 创建一个 PaletteDrawer,名字为 Sample。

b. 添加 ToolEntry 为 Sample 的子项。如图 9 的 new, quarter, half 项等等

c. 然后把这个 PaletteDrawer 添加到 PaletteRoot 中去,刷新 PaletteViewer 就可以看到新添加的 PaletteEntry 项如图 9


图 9 用户自定义 PaletteViewer
图 9 用户自定义 PaletteViewer

Sample 是为 CItem 新添加的属性,通过单击它可以显示/隐藏它所包含的 ToolEntry("new","quarter","half","three-quarters","full")。

定制 EditPolicy 和 Command

GMF 沿用了 GEF 中定义的 EditPolicy 和 Command 机制来完成对应用模型的操作和用户界面交互。这种设计提高了代码的复用和灵活性,同一个 EditPolicy 可以被安装到多个 EditPart 上,而同一个 Command 也可以被多个 EditPolicy 重用。

通常,每个 EditPart 都会安装一些类型的 EditPolicy,每个 EditPolicy 对应一个 Role,用一个字符串常量来标示,发送到 EditPart 的请求都会被根据类型委托给合适的 EditPolicy,由 EditPolicy 根据请求的特点决定操作细节和否创建 Command 并执行。

GMF 除了支持 GEF 的 EditPolicy 外,还对增加了种类丰富的 EditPolicy,常用的如:

CreationEditPolicy: 用于元素的创建操作,模型的同步,显示方式等。

DragDropEditPolicy: 对象拖放时的行为控制,模型的同步,用户界面交互等等。

SemanticEditPolicy: 处理语意相关的操作,如对模型元素的删除操作。

在创建一个 CItem 对象时,要求出现一个插入线提示即将创建新对象的位置如图 10,同时要求创建一个新 CItem 的同时,建立起它和 Row 对象的关联。而这些都是自动生成的代码所无法提供的。下面介绍如何通过 EditPolicy 和 Command 实现上面的两个要求。


图 10 自定义 Editpolicy 和 Command
图 10 自定义 Editpolicy 和 Command

1.添加插入线。当选中 palette viewer 上的 CItem 在编辑器里创建一个对象时,由安装在 CItem 上的 CreationEditPolicy 来处理这个请求,这里扩展一个新的 CItemCreationEditPolicy 来覆盖掉 CItem 原来的 CreationEditPolicy,提供对插入线的控制,见清单 9。由 updateInsertLine(…) 来控制插入线的位置和长度。


清单 9 CItemCreationEditPolicy

public class CItemCreationEditPolicy extends CreationEditPolicy {	protected Command getCreateCommand(CreateViewRequest request) {		Command cc = super.getCreateCommand(request);				// update insert line 		updateInsertLine(request);		TransactionalEditingDomain editingDomain = ((IGraphicalEditPart) getHost())|-------10--------20--------30--------40--------50--------60--------70--------80--------9||-------- XML error:  The previous line is longer than the max of 90 characters ---------|				.getEditingDomain();				CompoundCommand cm = new CompoundCommand();		cm.add(cc);				UpdateRefsCommand cmd1 = new UpdateRefsCommand(editingDomain,"Create Item",getDATable());		Command cmd2 = new ICommandProxy(cmd1);		cm.add(cmd2);				return new ICommandProxy(new CommandProxy(cm.unwrap())); 	}	private void updateInsertLine(CreateViewRequest creq) {				IGraphicalEditPart igp = (IGraphicalEditPart)getHost();		Point location = getPreferredLocation(creq);		int width = igp.getFigure().getBounds().width;				if(location == null){			ZachmanUtil.getCurrentDiagramEditor().bgLayer.eraseInsertedLine();			return ;		}				Point startP = location;		Point endP = new Point (startP.x+width,startP.y);				ZachmanUtil.getCurrentDiagramEditor().bgLayer.setLineToShow(startP, endP);		|-------10--------20--------30--------40--------50--------60--------70--------80--------9||-------- XML error:  The previous line is longer than the max of 90 characters ---------|		ZachmanUtil.getCurrentDiagramEditor().bgLayer.repaint();			}	}


2. 同步 CItem 和 Row 的关联。清单 9 中,在创建完一个 CItem 对象后,在 CommandStack 里面添加 UpdateRefsCommand 来建立 CItem 对象和 Row 的关联,由 UpdateRefsCommand 来完成关联的创建。


清单 10 UpdateRefsCommand

public class UpdateRefsCommand extends AbstractTransactionalCommand {		...		@Override	protected CommandResult doExecuteWithResult(IProgressMonitor monitor,			IAdaptable info) throws ExecutionException {		updateRefs(getCurrentItem());		return CommandResult.newOKCommandResult();	}		private updateRefs(CItem item){		Row row = getRow(item);		item.setRRow(row);		row.getRItem().add(item);	}	...	}


模型转换

Zachman View 希望能把第三方的 zachman model 导入到图 3 的 zachman view ecore model 来使用和管理,因此,这里需要做模型的转换与显示。

GMF 的模型分两部分,一部分是 ecore model,一个是用于显示信息的 notation(diagram) model,也就是 diagram 的信息。在把一个模型转换成 zachman view ecore model 并在 diagram 中显示和管理,这里需要处理两件事情:

1. 把第三方 model 转换成 zachman view 的 ecore model;

2. 需要根据生成的 zachman view 的 ecore model 生成对应的 notation model。

ecore model 间的转换有很多方法,IBM 提供了一个很好的工具来做这种转换:MTF(Model Transformation Framework),可参见Model Transformation with the IBM Model Transformation Framework来做 ecore model 的转换。下面将重点介绍如何利用 GMF API 生成 notation model 的方法。

GMF 生成的 ZachmanDiagramEditor 中有一个 setDocumentProvider(IEditorInput input) 方法,这个方法为 editor 设置了一个 DocumentProvider,由这个 provider 负责 model 的保存和读取的操作。所以,这里计划在读取 zachman model 时,动态生成其对应的 notation model。

1.当双击 zachman model 文件时,DocumentProvider 使用 setDocumentContent(IDocument document,IEditorInput editorInput) 方法来载入 model 文件,其调用 DiagramIOUtil 的 load(…) 方法来完成文件到 Resource 的载入。在载入过程中,load 方法会查找 model 中的 notation model,如果没有查找到,会抛出一个 NO_DIAGRAM_IN_RESOUCE 异常。在由 zachman ecore model 生成 notation model 的过程中,我们捕获这个异常,在其处理方法中完成对 notatoin model 的创建。

2. 为 zachman ecore model 创建一个 diagram,如下,首先根据 ecore model 创建一个 diagram,然后根据 ecore model 为 diagram 添加具体的 notation model 的信息。


清单 11 创建 diagram

private Diagram getDiagramForECoreModel(Zachman model) {		Diagram diagram = null;		diagram = ViewService.createDiagram(								(EObject) model,								"zachman",				ZachmanDiagramEditorPlugin.DIAGRAM_PREFERENCES_HINT);		// genarate diagram notation model		generateDiagramModel(model, diagram);		return diagram;}


3. 由 ecore model 生成 notation model,下面的代码是创建 model 中 Column 元素对应的 View model 的代码,这里需要注意的是要根据不同的元素类型,创建相应的 CreateElementRequestAdapter,它包含了创建 View 的一些必须信息。


清单 12 为每个 model 元素创建对应的 View

private void generateDiagramModel(Zachman model, Diagram diagram) {	boolean persisted = true;		// create column view model	Iterator dit = model.getColumns().iterator();	ColumnViewFactory dvf = new ColumnViewFactory();	CreateElementRequestAdapter dSemanticAdapter = dvf			.getCreateSemanticAdapter();	IElementType dElementType = ZachmanElementTypes.Column_1001;	String dSemanticHint = ((IHintedType) dElementType).getSemanticHint();	while (dit.hasNext()) {		Column column = (Column) dit.next();		dSemanticAdapter.setNewElement(column);		View view = dvf.createView(dSemanticAdapter, diagram, dSemanticHint, -1,				persisted, dvf.getColumnPreferencesHint());	}}


4. 设置 Document 的内容,在步骤1中的 setDocumentContent(…) 方法中的 document 参数有 setContect() 方法,对步骤3返回的 diagram,调用 document.setContent(diagram)。至此,view model 已经加入到 document 中,保存 model,再次打开 model 可见已经可以在 Zachman Diagram 中显示 model 的图形信息,并可进行相应的管理和操作。

这里需要注意的是,因为创建 notation model 是对 Resource 的修改,需要在一个事务中完成以上操作。




回页首



总结

GMF 为开发基于模型的图形化编辑器提供的便利,它为 EMF 和 GEF 间架起了一座沟通的桥梁,方便开发人员更好的集中精神在关键问题上,而不用考虑框架结构,界面一致性等情况。同时 GMF 允许开发人员直接使用 GEF 和 EMF 的接口去完成一些底层的复杂的功能。GMF 功能也在不断的完善和增强,希望我们的在本文的尝试会帮助大家解决在 GMF 开发过程可能会遇到的问题。

  • 10:18
  • 评论 / 浏览 (0 / 578)
本文在 GMF2.0 的基础上,用一个自上而下的流程分析建模工具为例,完整的描述了从如何建模,如何修改模型,以及如何客户化生成的代码框架的整个过程,主要涉及布局,UI 外观,模型操作以及对多个 Editor 的支持等等。

前言

GMF(Graphical Modeling Framework)是Eclipse的一个开源项目,它在结合了EMF和GEF的基础上,为基于模型的图形化编辑器的开发提供了一个功能强大的框架,开发人员可以采用建模的方式很容易的生成高质量的代码框架。

GMF主要由开发工具和运行时两部分组成。开发工具负责基于GMF核心模型的设计和建模工作,包括:描述具体领域模型的 Graph 模型的表示,Tool 模型的表示,以及如何用 Mapping 模型对前面三个模型进行映射组合,从而根据定义好的 Mapping 模型生成 Gen 模型,并最终完成代码框架的生成。三个独立的模型设计大大提高了模型的可重用性,开发人员可以在各模型所关注的领域对其进行进一步的定制,这个将在深入了解GMF建模工具中有详细介绍。GMF的运行时环境提供了丰富的组件(如Command框架,Services, Global Action, Layout等等)和强大的扩展机制,如 org.eclipse.gmf.runtime.emf.ui.modelingAssistantProviders, org.eclipse.gmf.runtime.common.ui.services.parserProviders,org.eclipse.gmf.runtime.diagram.core.viewProviders 等等,这些将在深入了解GMF运行时框架中详细地介绍。

刚发布的GMF2.0 在 GMF1.0 的基础上进一步提高了其易用性,同时也加入了相当的新功能。比如:Diagram 内容导航的支持,对 RCP 应用的支持,对 Preference Pages 的支持,支持模型的合并等等。

本文主要基于 GMF2.0 的运行环境,介绍如何实现一个自上而下的 Process 分析工具。其主要功能是:自上而下的分解一个大的 Process 为一批子 Process,每个 Process 可以由一个具体的 Flow 来描述,对于同一个模型,可以由多个Editor去编辑,如Process Editor, Flow Editor。期望的结果如下图。


图 1.Process Editor
Process Editor

图 2.Flow Editor
Flow Editor

本文将从定义领域模型入手,一步一步地介绍实现的细节。




回页首



GMF建模

GMF框架提供了对一个具体的Ecore模型进行图形化建模的支持,以模型驱动的方式,一步一步完成功能描述,直至代码的生成。参见(Graphical Model Framework进阶)。本文的环境如下:

  • Eclipse3.3.0
  • EMF2.3.0
  • GEF3.3.0
  • GMF2.0

ECore模型

ECore模型是GMF建模的起点,通常ECore模型是一个领域模型,描述了要建模的领域特征。其后的GMF借鉴了MVC的思想,Ecore模型就是MVC中的Model,详情可参见(深入了解GMF建模工具)。

用EMF向导新建一个空的process.ecore文件,右键单击该Ecore文件,选择Initiallize ecore_diagram diagram file,将会打开一个Ecore Diagram编辑器,创建Process的ECore模型如下:


图 3.Process的ECore模型
Process的ECore模型

当然,开发人员也可以直接用传统的树形EMF编辑器构上面的Ecore模型,或用RSA(Rational Software Architect)对模型进行UML图的描述,再转换成Ecore模型。

Graph模型

Graph模型是用来描述领域模型的显示信息,在GMF的定义中,Graph模型原则上是一个独立的模型,它描述了一个用于信息显示的模型。从它的定义里面就可以看出,它描述了Diagram, Node, Connection和Figure等图形化信息。将其关联到具体的领域模型上,就表示了该领域模型前端的显示图元。

选择已经定义的领域模型,打开向导对话框,


图 4.GMF模型向导
GMF模型向导

图4中,GMF提供了一系列向导来辅助创建不同的GMF模型。值得一提的是,GMF2.0支持对已有的Graph模型和Tool模型的提供更新操作。对于一个已存在的模型,新的更新会被自动添加到模型中,而原有的部分不会影响,参见(GMF2.0新特性介绍)。在具体的实现上,GMF2.0添加了Reconcile Graphical Definition Model和Reconcile Tool Definition Model两个向导来完成此功能,和GMF1.0相比,这个设计更人性化。

在具体的操作中,以上面定义的 ECore 模型为基础,利用 GMF 向导一步一步的生成对应的Graph模型如下图5,操作细节不再赘述。当然,也可以不用ECore模型,直接编辑一个空的Graph模型来进行定制,因为原则上Graph模型是独立的,可以被多个 Ecore 模型和Mapping 模型公用的。GMF向导以 ECore 模型为基础,方便了开发人员进行更有效的开发。


图 5.自定义Graph模型
自定义Graph模型

在Graph模型中,选择Map元素为根元素,这里只是简单的为每个元素定义了图元信息。关于图元外观的定制会在下面介绍。

Tool模型

图形化建模工具里有一个很重要的部分,就是Palette部分,它定义一系列的Tool Entry项,每个Tool Entry项对应工具所支持的某一类型对象的创建,如RSA Class Diagram的Palette里的Package, Class, Interface 等Tool Entry项。

GMF提供了对Palette部分的功能建模。Tool模型提供了对将要在Diagram里面出现的Tool Entry项的描述,包括名称,描述等。同时,GMF Tool模型提供了对常用Tool Entry的支持,此外,还有一些标准的Tool Entry项,如放大/缩小,选择,注释等等。Tool模型也是一个独立的模型,可以被多个ECore模型和Mapping模型共享。


图 6.自定义GMF Tool模型
自定义GMF Tool模型

这里使用GMF Tool模型创建向导为ECore模型生成了一个Tool模型。可以在下面的属性也里面对Tool Entry做一些基本的配置,不过,上面的Default Image是不允许配置的,GMF自动为每个Tool Entry生成了默认的图标,如果要自己定义图标,可以使用Bundle Image来指定特定的图标。

Mapping模型

Mapping模型是一个组织者,针对前面提到的三个独立的模型,由Mapping模型把这三个模型合理的组织起来。在Mapping模型里,孤立的模型间有了相互的联系,领域模型的对象将会由特定的Graph模型里的图元来进行展示,同时,还在Tool模型里为其设定一个Tool Entry作为Palette上的元素创建选项。

此外,GMF Mapping模型还提供了丰富灵活的配置选项,详情参见(深入了解GMF建模工具)。


图 7.Process的Mapping模型
Process的Mapping模型

上图中,只提供了在一个Diagram Editor里面创建一个Process节点的功能,实际需要每个Process节点下可以创建多个子节点。通常这种情况下,需要在Node Mapping下创建一个Child Reference和Compartment Mapping来定义节点所包含的子节点的信息,但是这样一来,在Diagram的显示上将会是一个Process节点图形内部包含了其所有的子 Process节点,它们在前端图形显示上是一个包含关系,不能满足我们所期望的层次关系的表示。所以这里只是在Diagram定义了Process的节点的信息,其父子节点的层次关系将在代码部分解决。

在这个例子中,我们需要在双击一个Process节点时,打开一个Flow Editor来对应Process的内部流程的编辑,因此,需要再定义一个Flow Editor,对应的Mapping模型如下图8


图 8.Flow Mapping模型
图片示例

在这个Flow里面关注3个元素,Task、Decision和表示它们间流程关系的Relationship。Flow Mapping模型共享了Process的Ecore模型和Graph模型。

Gen 模型

Gen模型是用来生成Diagram框架的模型,Gen 原模型描述了生成Diagram代码框架所需要的一些元素。在GMF中,Gen模型由Mapping模型生成,一个Mapping模型对应一个Gen模型,当然也可以由一个Mapping模型生成多个自定义的Gen模型,这样就可以生成多个Diagram Editor了。


图 9.Process Gen模型
Process Gen模型

图 10.Flow Gen模型
Flow Gen模型

Gen模型和Mapping模型相比,有更多关于代码生成的属性信息,开发人员可以自己定义生成代码的一些细节信息。在GMF2.0里,加入了内容导航元模型,在Gen模型里会生成一个Gen Navigator项,其下的一系列子项详细定义了导航栏和编辑器间作内容同步时的配置信息。其次,在Gen Diagram下面,还增加了对Preference Page的支持,开发人员可以自己配制和定制Diagram的Preference page页面,GMF提供了预定义的一组标准的Preference page,如Appearance, Printing, Connections等,当然,开发人员也可以预定义自己的Preference Page的配置情况。GMF将会生成相应的代码框架,开发人员只需把注意力集中在具体的应用逻辑上。值得一提的是,GMF Gen模型提供了对RCP应用在Gen模型的支持,通过对Gen Application项的定义和配置,可以很容易实现对RCP应用的设置,比如菜单项,工具条等等。

在一个GMF工程下面可以有多个Tool/Graph模型,同时也可以有多个Mapping模型和对应的Gen模型。事实上,本实例中就是创建了两个Mapping模型和Gen模型,分别用于Process Diagram和Flow Diagram的建模。




回页首



自定义编辑器的布局

在生成的代码框架中,GMF Diagram的默认布局管理器是FreeFormLayoutEx,在此布局管理器中,图元采用XY布局管理。这个和Process预期的自上而下的树形布局不相符,因此需要对Process Diagram的图元布局管理器进行一定的修改。

Node的布局

Node的布局是由它的父元素对应的图元的布局管理器来控制的,在Process Diagram里Node就是Process图元,它的父容器就是Diagram,这样,通过修改Diagram的布局管理器就规范了其子节点(Process节点)的布局了。可参见(GMF进阶-自定义布局管理器和背景色

所不同的只是Layout()方法的实现,本示例中用树形的布局来组织Process节点的显示。

Connection的布局

Connection图元是一个由至少两个点组成的线/折线,并且有可能带有一个箭头指向目标节点。折线上点的位置和数量决定了折线的形状和布局。默认的两个图元之间的一个连接是一条带箭头的直线,要实现图所示的Connection的折线型布局,这里需要修改Connection的布局管理器。

首先自定一个布局管理器,在这个布局管理器里会自动计算出折线的形状。


清单 1. 自定义Connection的布局

                  public class ConnectionLayout extends DelegatingLayout {    public void layout(IFigure parent) {  		...      Connection conn = (Connection)parent;      points = new PointList();      while(条件成立){      Point p[i] = //计算第i个节点坐标  		  Points.add(p[i]);      }      ...       conn.setPoints(points);    }  }      


其次,和Node的布局类似,把此布局管理器安装到Connection的图元上,如下所示。


清单 2. 安装Connection的布局

                public class RelationshipConnection extends PolylineConnectionEx {  public RelationshipConnection() {    setLayoutManager(new ConnectionLayout());  }}      


这样,在Connection进行布局的时候就会把原来的直线通过计算变成折线形状,照此方法,可以很容易的构造出水平或垂直方向的树形布局。

自定义Layout provider

在GMF生成的Diagram Editor里,在编辑模型的时候,可以选中一些或全部模型,在选择工具条上的link icon按钮或右键菜单里的Format->Arrange->All,就可以对选中的模型元素进行布局。

事实上,GMF对此功能提供了一个扩展点 org.eclipse.gmf.runtime.diagram.ui.layoutProviders进行支持,由这个扩展点可以轻松实现上述功能。下面实现一个CustomerLayoutProvider类,


图 11.自定义LayoutProvider
自定义LayoutProvider

这里需要注意两个方法

provides()方法,该方法检查当前的Provider是否提供具体的方法,检查选中的节点是否可以被布局,并且验证被传递的布局类型是否是有效的。

layoutLayoutNodes()方法,该方法是真正计算所要布局的节点的坐标从而进行布局的更新,和上面提到的做Node的布局是类似的。所不同的是此方法返回一个Runnable对象,由这个Runnable对象的执行,完成真正的布局功能的执行。具体的功能实现可参见GMF默认的LayoutProvider: org.eclipse.gmf.runtime.diagram.ui.providers.internal.DefaultProvider




回页首



UI外观的修改

GMF和GEF一样,都是用Draw2D来完成模型前端的显示工作,这里提到的对UI外观的修改主要是对Draw2D功能的一些应用。

在GMF中,每一个EditPart都会有一个Figure或PolylineConnection对象来做其前端的显示,因此对UI的外观的修改也就是对这些图形属性的修改。

设置渐变背景色

图2中,要实现图形背景的渐变色,需要对TaskEditPart对应的TaskFigure进行修改,在图形进行重新绘制时设置其背景色。这里Draw2D提供了fillGradient()方法来绘制渐变色。如下:


清单 3. 设置渐变的背景色

                  public void paintFigure(Graphics g) {    super.paintFigure(g);    Color oldForeground = g.getForegroundColor();    Color oldBackground = g.getBackgroundColor();    g.setForegroundColor(FlowUtil.FORE_COLOR);    g.setBackgroundColor(FlowUtil.TASK_BG_COLOR);    g.fillGradient(bounds, true);    g.setForegroundColor(oldForeground);    g.setBackgroundColor(oldBackground);  }      


设置子图形位置

默认情况下,Process和Task节点的图标和文本是放在同一行的,如何设置其为上下布局呢?修改它们对应的XXXNameEditPart的两个方法如下,设置文本的在下,图标在上:


清单 4. 设置文本和图标的布局

                  protected void setLabelTextHelper(IFigure figure, String text) {    if (figure instanceof WrapLabel) {      WrapLabel l = (WrapLabel)figure;      l.setText(text);      l.setTextPlacement(PositionConstants.SOUTH);      l.setTextWrap(true);    } else {      ((Label) figure).setText(text);    }  }  protected void setLabelIconHelper(IFigure figure, Image icon) {    if (figure instanceof WrapLabel) {      WrapLabel l = (WrapLabel)figure;      l.setIcon(icon);      l.setIconAlignment(PositionConstants.TOP);    } else {      ((Label) figure).setIcon(icon);    }  }      





回页首



自定义UI操作

GMF工具生成了一个很好的代码框架,但是为了使其更加符合具体的应用,需要对生成的代码进行一定的修改,尤其是对UI上的一些操作常常伴随着模型的修改。由于GMF是基于事务的机制对模型进行管理,所以,对模型的修改或更新操作需要放在一个具体的事务中进行。GMF丰富的EditPolicy和 Command机制提供了对上述的支持。

创建

模型的创建是由CreationEditPolicy来处理的,如果一个EditPart需要创建其子元素,则需为其安装一个CreationEditPolicy来控制子元素的生成。通常为


清单 5. 安装自定义的CreationEditPolicy

                  installEditPolicy(EditPolicyRoles.CREATION_ROLE,	new XXXCreationEditPolicy());      


其中,XXXCreationEditPolicy是对CreationEditPolicy的一个扩展,本实例中有个限制,需要在创建一个Process节点的同时,创建一条和它的父节点的连线,也就是一个带箭头Connection连接,实现getCreateCommand()如下。


清单 6. 自定义CreationEditPolicy

                  protected Command getCreateCommand(CreateViewRequest request) {    TransactionalEditingDomain editingDomain = ((IGraphicalEditPart) getHost())		                                                         .getEditingDomain();    CompositeTransactionalCommand cc = //Create Process command    ...    Command cmd = new ICommandProxy(cc.reduce());    CompoundCommand cm = new CompoundCommand();		    cm.add(cmd);        // add Create Connection command     if(request instanceof CreateViewAndElementRequest){		      CreateElementRequestAdapter requestAdapter =        ((CreateViewAndElementRequest)request)            .getViewAndElementDescriptor().getCreateElementRequestAdapter();      if(CBSDiagramUtil.getInstance().getSourceEP() instanceof DANode2EditPart){        CreateChildrenRelation4ProcessCommand cmd1 = createRalationCommand(                                     editingDomain,                                      (CreateViewAndElementRequest)request,                                          requestAdapter);        Command cmd2 = new ICommandProxy(cmd1);		  		        cm.add(cmd2);      }    }    return new ICommandProxy(new CommandProxy(cm.unwrap()));  }  private CreateChildrenRelation4ProcessCommand createRelationCommand(          TransactionalEditingDomain editingDomain, CreateViewAndElementRequest request,           CreateElementRequestAdapter requestAdapter) {    Diagram diagramView = (View)getHost().getModel()).getDiagram();	    IElementType type = ProcessElementTypes.Relation_3001;     CreateConnectionViewAndElementRequest req =           New CreateConnectionViewAndElementRequest(type,                   ((IHintedType) type).getSemanticHint(),                   request.getViewAndElementDescriptor().getPreferencesHint());    CreateChildrenRelation4ProcessCommand cmd =                new CreateChildrenRelation4ProcessCommand(editingDomain,requestAdapter,req,                                                      diagramView.getDiagram());		    return cmd ;  }      


在创建一个Create Process命令后,紧接着在生成一个创建Connection的命令,这样就会在创建子Process后自动创建一条与其父节点的连接。

这里需要说明的一点是,在创建一个Connection对应的模型信息是在对应EditPart的XXXItemSemanticEditPolicy生成的Command里完成的,GMF2.0里面这个Command是作为一个独立的Command放在command package下面的,和其他的CreateElementCommand类似。如果使用之前的GMF版本的话,这个Command是作为XXXItemSemanticEditPolicy的一个内部类存在的。

生成一个Connection对应的模型后,需要在Diagram里面创建一个其对应的Notation View的信息,也就是一个Edge对象,由这个Edge对象维护Connection的显示信息,包括连线形状,起始点和目标节点等。这和创建一个 Node的Notation View类似,需要指定CreateElementRequestAdapter, SemanticHint,然后由RelationshipViewFactoryCreateView()方法来生成一个Edge对象。所有的这些操作都是在CreateChildrenRelation4PorcessCommand类的doExecuteWithResult()方法里面进行,具体操作由CreateRelationView()方法完成,其中containerView为Diagram View,因为所有的Process View都是Diagram View的子节点。


清单 7. 创建Relation View

                  private void createRelationView() {    DARelationViewFactory drvf = new DARelationViewFactory();    final int index = -1 ;    boolean persisted = true ;    final String semanticHint = connReq.getConnectionViewAndElementDescriptor()                                                            .getSemanticHint();		    ConnectionViewAndElementDescriptor ccd =                                         connReq.getConnectionViewAndElementDescriptor();    final CreateElementRequestAdapter rAdapter = ccd.getCreateElementRequestAdapter();    PreferencesHint preferencesHint = ccd.getPreferencesHint();				    Node srcNode = getSrcView();    Node tgtNode = getTgtView();		    if(srcNode == null || tgtNode == null){      return ;    }    View view = drvf.createView(rAdapter, containerView, semanticHint,                                                    index, persisted, preferencesHint);    Edge edge = (Edge)view;    edge.setSource(srcNode);    edge.setTarget(tgtNode);				  }      


删除

在EditPolicy中,有一个SemanticEditPolicy,GMF生成的代码框架中常会以此EditPolicy为基础,派生出XXXBaseItemSemanticEditPolicy,进而派生出其他EditPart所需的具体的XXXItemSemanticEditPolicy。正是这个EditPolicy控制着元素的删除操作。安装在EditPart上的SemanticEditPolicy控制此EditPart被删除时所进行的操作。

在本实例中,要求在删除一个Process节点时删除它所关联的子节点,如果此Process节点有Flow的信息,删除其对应的Flow的信息,并关闭对应的Flow Editor(如果打开的话)。基于上述要求,首先需要自定义一个CustomizeDestroyElementCommand,由它来完成具体的删除操作。


清单 8. 自定义DestroyElementCommand

                public class CustomizeDestroyElementCommand extends DestroyElementCommand {  …  @Override  protected CommandResult doExecuteWithResult(IProgressMonitor monitor, IAdaptable info)                                                          throws ExecutionException {    ProcessEditPart editpart = (ProcessEditPart)getHost();    if(root == null) {      root = (ProcessEditPart)editpart.getParent();    }		    // delete all descents and their relative process flow    List children = editpart.getSourceConnections();    int size = children.size();    for(int i=0; i<size; i++) {      delete((ProcessEditPart)((RelationshipsEditPart)children.get(i)).getTarget());    }		    // update parent's process flow    List targetConns = editpart.getTargetConnections();    if(targetConns.size() != 0) {      ProcessEditPart parent =                 (ProcessEditPart)((RelationshipsEditPart)targetConns.get(0)).getSource();      Diagram flowDiagram = getOrDeleteProcessFlowDiagram(parent, false);      if(flowDiagram != null) {        deleteFlow(flowDiagram, ((Node)editpart.getModel()).getElement());      }    }		    // delete self process flow and close flow editor if acitved    Diagram flowDiagram  = getOrDeleteProcessFlowDiagram(editpart, true);    if(flowDiagram != null) {      closeEditor(flowDiagram);    }		    return super.doExecuteWithResult(monitor, info);			  }	}      


其次需要把上面的Command安装在正确的地方,如下,修改ProcessEditPart的XXXItemSemanticEditPolicy的方法如下,用CustomizeDestroryElementCommand替换掉原有的Command。


清单 9. 应用自定义的DestroyElementCommand

                  protected Command getDestroyElementCommand(DestroyElementRequest req) {    CompoundCommand cc = getDestroyEdgesCommand();    addDestroyShortcutsCommand(cc);    View view = (View) getHost().getModel();    if (view.getEAnnotation("Shortcut") != null) { //$NON-NLS-1$      req.setElementToDestroy(view);    }    cc.add(getGEFWrapper(new CustomizeDestroyElementCommand(req)));     return cc.unwrap();  }      





回页首



多编辑器的支持

很明显,这里需要处理多个编辑器同时打开一个模型的情况,我们希望可以在编辑Process树的时候,同时可以打开它的Flow Editor来编辑其具体的流程细节。但是如果用默认的方法给一个编辑器设置一个文件作为输入的话,这一点是绝对做不到的。因为Eclipse本身在第二次打开同样的文件时,如果发现它已经被一个Editor打开就不会再次打开它。

针对这种情况,一种比较直接的办法就是把一个模型拆分成一些小的部分,分别用文件保存,每个Editor之打开其中的一个文件,像WBM就采用了类似的做法。

还有一种就是整个模型就是一个文件,而用非文件的方式去打开和编辑模型,像RSA就是这种作法,整个模型就是一个大的.emx文件。本文计划采用一个模型文件的做法。

首先,需要一个EditingDomainManager来管理EditingDomain和文件所对应Resource。由这个Manager维护当前处于活跃状态的EditingDomain和Resource,Flow Editor共享其对应的Process Editor的EditingDomain。


清单 10. EditingDomain Manager

                public class EditingDomainManager {	  public TransactionalEditingDomain getCurrentEditingDomain(final String id){  ...  return editingDomain ;  }	  public Diagram getDiagramOfElement(Resource gmfRes, String editorID, Process element) {	    ...    return  getDiagram(gmfRes, kind,element);  }}      


其次,需要用DiagramEditorInput来替换默认的以文件为输入的FileEditorInput。双击一个Process图标的时,如果当前节点没有对应的Diagram,则由initialFlowDiagram()方法生成一个空的Diagram对象,然后构造DiagramEditorInput来打开Flow Editor。


清单 11. 打开Flow Editor

                  private void openFlowEditor() {    ...    IWorkbenchPage page = PlatformUI.getWorkbench()                               .getActiveWorkbenchWindow().getActivePage();		    if(!isFlowDiagramExisted(node)){      initialFlowDiagram(node);    }		    Diagram diagram = getProcessNodeDiagram(node);    DiagramEditorInput input = new DiagramEditorInput(diagram);    try {      IEditorPart ep = page.openEditor(input, editorId, true);    } catch (PartInitException e) {			      e.printStackTrace();    }		  }      


为了从DiagramEditorInput打开一个Editor,Flow Diagram Editor要修改它的getDiagram()方法,默认的getDiagram()方法没有提供对DiagramEditorInput的处理。


清单 12. 修改Flow Editor的方法

                  @Override  public Diagram getDiagram() {    IEditorInput input = getEditorInput();		    if(input instanceof DiagramEditorInput){      DiagramEditorInput dinput = (DiagramEditorInput)input;      return dinput.getDiagram();    }		    return super.getDiagram();  }      


此外还需要对FlowDocumentProvider进行相应的修改,重载三个函数如下


清单 13. 修改DocumentProvider

                  @Override  public IEditorInput createInputWithEditingDomain(IEditorInput editorInput,                                              TransactionalEditingDomain domain) {    return editorInput;  }	  @Override  public IStatus getStatus(Object element) {    return STATUS_OK;  }  @Override  public boolean isModifiable(Object element) {    if(element instanceof DiagramEditorInput){      return true;    }    return super.isModifiable(element);  }      


这样,就可以同时打开多个Process文件,并且编辑多个Flow Editor了,具体如图1图2所示。




回页首



结束语

GMF2.0在前一个版本的基础上,在可用性和功能上都有了很大的改进,本文用一个完整的例子介绍了GMF在从建模、模型修改到生成代码框架以及对代码定制的过程,对GMF的应用和开发做了一个系统的介绍。当然,GMF本身还在不断的完善中,新的功能也会不断的包含近来,希望能有越来越多的人学习和使用 GMF。

  • 16:05
  • 评论 / 浏览 (0 / 1489)

XML Schema学习笔记和注解-(转)

1、复杂型和简单型之间最根本的区别就是:复杂型的内容中可以包含其他元素,也可以带有属性(Attribute),但简单型既不能包含子元素,也不能带有任何属性,但限制条件或扩展条件还是可以有的。

一个复杂类型例子:
<xsd:complexType >
    <xsd:sequence>
        <xsd:element name="name"type="xsd:string"/>
        <xsd:element type="xsd:string"/>
        <xsd:element type="xsd:string"/>
        <xsd:element type="xsd:decimal"/>
    </xsd:sequence>
    <xsd:attribute name="country" type="xsd:NMTOKEN" fixed="US"/>
</xsd:complexType>
一个简单类型例子:
<xsd:simpleType >
    <xsd:restriction base="xsd:integer">
        <xsd:minInclusive value="10000"/>
        <xsd:maxInclusive value="99999"/>
    </xsd:restriction>
</xsd:simpleType> 


2、element存在约束:element可以通过其minOccurs和maxOccurs两个属性来约束元素实例存在的个数,这两个属性的缺省值都是1,表示默认情况下此元素在XML实例文档中必须出现一次。
例如:
<element minOccurs="1" maxOccurs="1" type="timeInstant"/>

3、attribute存在约束:元素属性也可以通过attribute的use属性来约束出现一次或根本不出现;use属性的取值可以是required,optional,prohibited三个值,缺省(默认)值是optional. 


4、 element和attribute都有一个default和fixed属性,不能同时存在。针对element来说,只有当element实例为空时才采用此 default值,而attribute是当实例不提供此attribute时才采用此default值,因此对attribute而言,只有其use值是optional时default值才有意义,而且对element和attribute来说fixed和default两个属性不能同时存在,否则会出现错误。 
fixed属性要求element或attribute如果出现的话,就一定要是fixed属性里指定的值。


5、全局元素和全局属性。直接定义在schema元素下,即schema元素的顶级子元素的element和attribute都是全局的,称之为全局元素和全局属性,你在其他型定义中可以直接引用。 

  <xsd:annotation>    <xsd:documentation xml:lang="en">     Purchase order schema for Example.com.     Copyright 2000 Example.com. All rights reserved.    </xsd:documentation>  </xsd:annotation>  <xsd:element  type="PurchaseOrderType"/>  <xsd:element  type="xsd:string"/>  <xsd:complexType >    <xsd:sequence>      <xsd:element  type="USAddress"/>      <xsd:element  type="USAddress"/>      <xsd:element ref="comment" minOccurs="0"/>      <xsd:element   type="Items"/>    </xsd:sequence>    <xsd:attribute  type="xsd:date"/>  </xsd:complexType>  <xsd:complexType >    <xsd:sequence>      <xsd:element    type="xsd:string"/>      <xsd:element  type="xsd:string"/>      <xsd:element    type="xsd:string"/>      <xsd:element   type="xsd:string"/>      <xsd:element     type="xsd:decimal"/>    </xsd:sequence>    <xsd:attribute  type="xsd:NMTOKEN"                   fixed="US"/>  </xsd:complexType>  <xsd:complexType >    <xsd:sequence>      <xsd:element  minOccurs="0" maxOccurs="unbounded">        <xsd:complexType>          <xsd:sequence>            <xsd:element  type="xsd:string"/>            <xsd:element >              <xsd:simpleType>                <xsd:restriction base="xsd:positiveInteger">                  <xsd:maxExclusive value="100"/>                </xsd:restriction>              </xsd:simpleType>            </xsd:element>            <xsd:element   type="xsd:decimal"/>            <xsd:element ref="comment"   minOccurs="0"/>            <xsd:element  type="xsd:date" minOccurs="0"/>          </xsd:sequence>          <xsd:attribute  type="SKU" use="required"/>        </xsd:complexType>      </xsd:element>    </xsd:sequence>  </xsd:complexType>  <!-- Stock Keeping Unit, a code for identifying products -->  <xsd:simpleType >    <xsd:restriction base="xsd:string">      <xsd:pattern value="\d{3}-[A-Z]{2}"/>    </xsd:restriction>  </xsd:simpleType></xsd:schema>

从上面这个例子可以看出,purchaseOrder和comment是全局元素,可以被其他element用ref属性引用,purchaseOrderType,USAddress,Items和SKU是全局属性,可以被其他element用type属性引用。

6、派生新型有两种方式:第一种就是直接从其他型中扩展(继承)而来,另外一种就是通过对已有型进行限定性约束而来。 
如:以下有三种通过限定性约束定义的新型: 
通过值范围限定: 
<xsd:simpleType >
    <xsd:restriction base="xsd:integer">
        <xsd:minInclusive value="10000"/>
        <xsd:maxInclusive value="99999"/>
    </xsd:restriction>
</xsd:simpleType> 
使用模式匹配限定: 
<xsd:simpleType >
    <xsd:restriction base="xsd:string">
        <xsd:pattern value="\d{3}-[A-Z]{2}"/>
    </xsd:restriction>
</xsd:simpleType> 
使用枚举方式限定: 
<xsd:simpleType >
    <xsd:restriction base="xsd:string">
        <xsd:enumeration value="BeiJing"/>
        <xsd:enumeration value="NanChang"/>
        <xsd:enumeration value="ShangHai"/>
    </xsd:restriction>
</xsd:simpleType> 

7、原子型(不可分割的型,象string,integer等系统内建的型)、列表型、联合型合起来统一称为简单型。在Schema中有NMTOKENS、IDREFS、ENTITIES三种内建的列表型,你也可以从已有的简单型来创建list(列表)型,但你不能从已有的list型和复杂型来创建列表(list)型。 
如: 
<xsd:simpleType >
    <xsd:list itemType="myInteger"/>
</xsd:simpleType> 
在XML实例文档中列表型的值是通过空格来进行分隔的,如果声明了一个listOfMyIntType元素,其值可能是: 
<listOfMyInt>20003 15037 95977 95945</listOfMyInt> 

8、约束List类型。有几个方面的元素可以应用于list型来进行约束,它们是:length、minLength、maxLength和enumeration,如: 
<xsd:simpleType >
    <xsd:list itemType="USState"/>
</xsd:simpleType>
<xsd:simpleType >
    <xsd:restriction base="USStateList">
        <xsd:length value="6"/>
    </xsd:restriction>
</xsd:simpleType> 
注:针对列表型要千万注意成员是string型的,因为string型中的空格和列表型的分割符空格会造成部分混淆。

9、对元素的定义可以采用通过指定其type属性为已定义的属性的方式(基本类型或自定义的全局属性),也可一采用匿名定义型的方式,如: 
采用型定义: 
<xsd:element name=”comment” type=”xsd:string”> 
采用匿名定义: 
<xsd:element
    <xsd:simpleType>
        <xsd:restriction base=”xsd:positiveInteger”>
            <xsd:maxExclusive value=”100” />
        </xsd:restriction>
    </xsd:simpleType>
</xsd:element> 

10、union(联合)表示在XML实例文档中的元素实例符合union型定义的成员型中的一种就可以了(合法),这一点和C++中的联合型有似的概念,如: 
<xsd:simpleType >
    <xsd:union memberTypes="xsd:string integer"/>
</xsd:simpleType> 

11、复杂型一般可以分为三
第一是包含字符内容和属性但不包含子元素,针对第一可以通过simpleContent来实现;
第二是包含属性和子元素但不包含字符数据(字符数据包含在子元素中),通过complexContent来做到;
第三是即包含属性和字符内容又包含子元素的,只需要将complexType的属性mixed设为true就可以了;
具体的例子如下: 

第一种型(从一个简单型扩展而来,增加了属性): 
<xsd:element >
    <xsd:complexType>
        <xsd:simpleContent>
            <xsd:extension base="xsd:decimal">
                <xsd:attribute type="xsd:string"/>
            </xsd:extension>
        </xsd:simpleContent>
    </xsd:complexType>
</xsd:element> 

第二种型(有一个element和两个attribute构成): 
<xsd:element >
    <xsd:complexType>
        <xsd:complexContent>
            <xsd:element ?type=”xsd:string” />
            <xsd:attribute type="xsd:string"/>
            <xsd:attribute ?type="xsd:decimal"/>
        </xsd:complexContent>
    </xsd:complexType>
</xsd:element> 
注意:在这里由于默认情况下缺省是complexContent,所以在这里简略的写法是: 
<xsd:element >
    <xsd:complexType>
        <xsd:element />
        <xsd:attribute type="xsd:string"/>
        <xsd:attribute ?type="xsd:decimal"/>
    </xsd:complexType>
</xsd:element> 

第三种型: 
<xsd:element >
    <xsd:complexType mixed="true">
        <xsd:sequence>
            <xsd:element >
                <xsd:complexType mixed="true">
                    <xsd:sequence>
                        <xsd:element type="xsd:string"/>
                    </xsd:sequence>
                </xsd:complexType>
            </xsd:element>
            <xsd:element type="xsd:positiveInteger"/>
            <xsd:element type="xsd:string"/>
            <xsd:element type="xsd:date" minOccurs="0"/>
        </xsd:sequence>
    </xsd:complexType>
</xsd:element> 
第三种型的实例可能如下: 
<letterBody>
    <salutation>Dear Mr.
    <name>Robert Smith</name>.
    </salutation>
    Your order of
    <quantity>1</quantity>
    <productName>Baby Monitor</productName>
    shipped from our warehouse on
    <shipDate>1999-05-21</shipDate>
</letterBody>

12、根据11的描述那么要定义一个空内容的元素,也就是说定义一个只包含属性的元素,只要在complexContent中不包含任何子元素,就可以了,如(注:complexContent在complexType外面被省略了):
<xsd:element >
    <xsd:complexType>
        <xsd:attribute type="xsd:string"/>
        <xsd:attribute type="xsd:decimal"/>
    </xsd:complexType>
</xsd:element>

13、anyType是所有Schema型的基型,和Java中的Object似。因此,以下定义: 
<xsd:element name="anything" type="xsd:anyType"/> 
可以写成: 
<xsd:element name="anything"/> 

14、Schema中用annotation、document、appInfo三个元素来进行注释,其中appI和document都是作为annotation的子元素来处理的。并且annotation一般是作为schema的顶层子元素、element的构造、型定义的顶层子元素的。 
如: 
<xsd:element >
    <xsd:annotation>
        <xsd:documentation xml:lang="en">
            element declared with anonymous type
        </xsd:documentation>
    </xsd:annotation>
    <xsd:complexType>
        <xsd:annotation>
            <xsd:documentation xml:lang="en">
                empty anonymous type with 2 attributes
            </xsd:documentation>
        </xsd:annotation>
        <xsd:complexContent>
            <xsd:restriction base="xsd:anyType">
                <xsd:attribute type="xsd:string"/>
                <xsd:attribute type="xsd:decimal"/>
            </xsd:restriction>
        </xsd:complexContent>
    </xsd:complexType>
</xsd:element> 

15、 choice,all
choice仅允许在实例文档中使用其中一个子元素;在all中的所有元素都可以出现一次或一次都不出现,并且其中元素实例是没有顺序约束的,而且all 必须放在任何内容模型的最顶层,为了说明这个问题,下面先列出一个合法的,然后列出一个不合法的以供对照说明: 
<xsd:complexType >
    <xsd:all>
        <xsd:element type="USAddress"/>
        <xsd:element type="USAddress"/>
        <xsd:element ref="comment" minOccurs="0"/>
        <xsd:element type="Items"/>
    </xsd:all>
    <xsd:attribute type="xsd:date"/>
</xsd:complexType> 
下面是一个不合法的: 
<xsd:complexType >
    <xsd:sequence>
        <xsd:all>
            <xsd:element type="USAddress"/>
            <xsd:element type="USAddress"/>
            <xsd:element type="Items"/>
        </xsd:all>
        <xsd:sequence>
            <xsd:element ref="comment" />
        </xsd:sequence>
    </xsd:sequence>
    <xsd:attribute type="xsd:date"/>
</xsd:complexType>

 

16、attributeGroup属性组。在存在很多型中都有几个相同的型的情况,可以采用属性组的方式来进行重用,属性组定义的格式是: 
<xsd:attributeGroup
    <xsd:attribute type=”xsd:string” />
    <xsd:attribute type=”xsd:string” />
    …
</xsd:attributeGroup> 

使用可以采用以下方式: 
<xsd:element
    <xsd:comlexType>
        <xsd:element />
        <xsd:attributeGroup ref=”attrGroupName” />
    </xsd:complexType>
</xsd:element> 

17、关于include的用法 
include元素可以将外部的定义和声明引入到文档中,并且作为新Schema文档的一部分,但必须注意的一点是,被包含成员的目标命名空间TargetNamespace必须和包含的目标命名空间schemaLocation一样。具体写法例子是: 
<include schemaLocation=“http://www.example.com/schemas/address.xsd” /> 

18、extension。如果一个型是从另一个型扩展而来的,那么定义为父型的element,在实例文档中,可以通过符合子型的element实例来代替,但必须通过xsi:type指明此元素的具体型。例如: 
<xsd:complexType
    <xsd:sequence>
        <xsd:element type=”xsd:string” />
        <xsd:element type=”xsd:string” />
    </xsd:sequence>
</xsd:complexType> 
<!-- 扩展型定义 --> 
<xsd:complexType
    <complexContent>
        <xsd:extension base=”Person”>
            <xsd:sequence>
            <xsd:element
                <xsd:restriction base=”string”>
                    <xsd:enumeration value=”male” />
                </xsd:restriction>
            </xsd:element>
            </xsd:sequence>
        </xsd:extension>
    </complexContent>
</xsd:complexType>
<!-- 型的声明 --> 
<xsd:element name=”human” type=”Person” /> 
在XML实例文档中针对human可以是这样的(和面向对象有似的概念): 
<human xsi:type=”Father”>
    <name>xiaogen</name>
    <gender>male</gender>
</human> 
19、关于置换组 substituionGroup
XML Schema 提供了一种机制叫置换组,允许原先定义好的元素被其他元素所替换。更明确的,这个置换组包含了一系列的元素,这个置换组中的每一个元素都被定义为可以替换一个指定的元素,这个指定的元素称为头元素(Head Element),需要注意的是头元素必须作为全局元素声明,注意,当一个实例文档包含置换元素时替换元素的型时从它们的头元素那里派生的,此时并不需要使用我们前面所说的xsi:type来识别这些被派生的型,当定义了置换组之后,并非意味着不能使用头元素,而只能使用这个置换组中的元素,它只是提供了一个允许元素可替换使用的机制。例如: 
<xsd:schema>
    <xsd:element type=”xsd:string”/>
    <xsd:element type=”xsd:string” substitutionGroup=”comment” />
    <xsd:element type=”xsd:string” substituionGroup=”comment” />
    <xsd:element
        <xsd:complexType>
            <xsd:element type=”xsd:string” />
            <xsd:element type=”xsd:decimal” />
            <xsd:element ref=”comment” />
            <xsd:element type=”xsd:date” />
        </xsd:complexType>
    </xsd:element>
</xsd:schema> 
下面是实例文档的一个例子: 
<order>
    <productName>Lapis necklace</productName>
    <price>999</price>
    <shipComment>Use gold wrap if possible</shipComment>
    <customerComment>Want this for the holidays!</customerComment>
    <shipDate>2004-08-15</shipDate>
</order> 

20、抽象元素和抽象型 

当一个元素或者型被声明为“abstract”时,那么它就不能在实例文档中使用。当一个元素被声明为”abstract”的时候,元素的置换组的成员必须出现在实例文档中。当一个元素相应的型被定义声明为"abstract"时,所有关联该元素的实例必须使用"xsi:type"来指明一个型,这个型必须是非抽象的,同时是在定义中声明的抽象型的派生型。 
一、如将上面的comment元素的声明更改成: 
<xsd:element name=”comment” type=”xsd:string” abstract=”true” /> 
那么上面的order实例中就只能包含customerComment和shipComment才是有效的。 
二、如果有下面的型定义: 
<schema xmlns="http://www.w3.org/2001/XMLSchema"  targetNamespace="http://cars.example.com/schema"  xmlns:target="http://cars.example.com/schema"> 
    <complexType abstract="true"/> 
    <complexType > 
        <complexContent>
            <extension base="target:Vehicle"/>
        </complexContent>
    </complexType>
    <complexType > 
        <complexContent>
            <extension base="target:Vehicle"/>
        </complexContent>
    </complexType>
    <element type="target:Vehicle"/> 
</schema> 
根据以上的定义和声明,下面的实例片断就是不能通过验证的: 
<transport xmlns="http://cars.example.com/schema" /> 
下面经过修改的就可以通过验证了。 
<transport xmlns=“http://cars.example.com/schema”  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Car"/> 

21、Final。为了阻止型被派生(包括通过限制派生和通过扩展派生),可以使用final来设置(有点和java中的final的概念似),其值有三个:restriction,extension,#all。在模式文件的根元素schema元素中有一个可选的finalDefault属性,它的值能够取为final属性所允许的几个值之一。指定finalDefault属性的值的效果等于在模式文档中每个型定义和元素声明中指定final属性,同时其值为finalDefault属性的值。如果想阻止前面的Person被限制派生,我们需要修改定义为如下: 
<xsd:complexType final=”restriction”>
    <xsd:sequence>
        <xsd:element type=”xsd:string” />
        <xsd:element type=”xsd:string” />
    </xsd:sequence>
</xsd:complexType> 

22、block。因为在实例文档中与父型关联的元素(也就是声明为父型的元素)可以通过派生的子型来替代,这在2中已经有详细的说明和例子。同时,XML Schema也提供了一种机制来控制派生型以及置换组在实例文档中使用的机制。这种机制是通过型的block属性来控制的,该属性可以取值为:restriction,extension,#all。和final属性一样,在模式文档的根元素 schema元素里有一个可选的属性blockDefault,它的值为block属性所允许的值中的一个。指定blockDefault属性的作用等价于在模式文档中为每个型定义和元素声明指定block属性。如在前面例子中我们想阻止在使用Father来替代Person的话我们需要修改Person的定义如下: 
<xsd:complexType block=”extension”>
    <xsd:sequence>
        <xsd:element type=”xsd:string” />
        <xsd:element type=”xsd:string” />
    </xsd:sequence>
</xsd:complexType> 
取值为“extension”的block属性将阻止在实例文档中使用通过扩展的派生型来替换Person(即可以阻止Father来替换Person),然而它不会阻止通过约束的派生来替换Person。 

23、用fixed禁止派生。另外一种派生型的控制机制是应用于简单型方面的派生。当定义一个简单型的时候,我们可以使用fixed属性对它的所有定义的参数进行修饰,以阻止这些参数在型派生中被修改,例如: 
<xsd:simpleType
    <xsd:restriction base=”xsd:string”>
        < xsd:length value=”7” fixed=”true” />
    </xsd:restriction>
</xsd:simpleType> 
当这个简单型被定义之后,我们能够派生出一个新的邮编型,在其中我们使用了一个没有在基本型中固定的参数: 
<xsd:simpleType >
    <xsd:restriction base=" Postcode">
        <xsd:pattern value="[A-Z]{2}\d\s\d[A-Z]{2}"/>
    </xsd:restriction>
</xsd:simpleType> 
然而,我们不能购派生出一个这样的新的邮编型:在其中我们重新定义了任何在基型中已经被固定(fixed)的参数: 
<xsd:simpleType >
    <xsd:restriction base="ipo:Postcode">
        <xsd:pattern value="[A-Z]{2}\d\d[A-Z]{2}"/>
        <!-- illegal attempt to modify facet fixed in base type --> 
        <xsd:length value="6" fixed="true"/>
    </xsd:restriction>
</xsd:simpleType>

  • 11:20
  • 评论 / 浏览 (0 / 438)

刚刚接触eclipse的这些概念,着实有点头晕

现在总算对这一部分有了一点具体的认识了,写出来,顺便理一下思路:


emf 用于创建模型
gef是eclipse中表示可编辑图形界面的非常强大的一种工具,把具体的工作划分为MVC三层模式.

所以在通常的开发中,一般是将两种技术合起来用 , 简称GMF .

emf的建模
       emf建模可以采用多种方式,也有不少现成的工具,比如:eclipse uml ,下载个人版的就基本够用了,但是不能于小组开发(对cvs有限制),当然要是诚心一点也可以找到破解版.....(呵呵,个人言论,没用过,也不推荐),建模部分唯一要注意的是对其它类的引用(比如说emf.draw2d.Rectangle),其实是可以直接引用的.

       三下两下建模完毕了,如果是用  .xsd 格式写的( 辛苦了......) 那么在eclipse装完emf的插件后,新建emf项目,导入xsd文件,就可以生成模型的代码了,要是熟悉了以后,建模还是很方便的.当然 eclipse里也提供rose, ecore, 以及 uml  类图的直接导入.
   
       模型部分通常一定会建立control的监听,我的做法是在写类图的时候让AbstractModel继承一个Adaptor,这样生成后的模型就会自动加入监听了.

       emf提供了非常良好的数据的串行化和反串行化,EMF 能够处理多重模型源,例如XML模型,所以指定使用哪些资源来实现(反)串行化你的数据也是很重要的。通常,当你调用 ResourceSet.createResource(URI)方法时,它查询Resource.Factory.Registry来查找一个工厂-该工厂是为该URI而注册的并且使用它来创建一个适当的资源实现。因此,在你(反)串行化你的数据以前,请确保你已注册了适当的资源工厂实现。EMF提供若干Resource.Factory实现:

  ·对于XML数据,使用org.eclipse.emf.ecore.xmi.impl.XMLResourceFactoryImpl。

  ·对于XMI数据,使用org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl。

  ·对于Ecore模型,使用org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl。

    对于刚入门的人来说(比如说我啦),对调用那些什么工厂之类嫌太麻烦的话,只接用xmlEncoder 进行编码,解码也是一种不错的方式,贴一段代码:
    //   保存  

    XMLEncoder encoder=null;
        try {
            encoder = new XMLEncoder(
                    new BufferedOutputStream(
                            new FileOutputStream("d://Sample.xml")));
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        encoder.writeObject(contentsModel);
        encoder.close();

//解析
    XMLDecoder decoder = null;
      
        try {
            decoder = new XMLDecoder(
                        new BufferedInputStream(
                          new FileInputStream("d://Sample.xml")));
                contentsModel = (ContentsModel)decoder.readObject();
                decoder.close();
        } catch (FileNotFoundException e) {
            contentsModel = new ContentsModel();
        }

       最后保存成标准的xml,  它也有自已的dtd; 没写出来,但是解析器都能够识别的
        其实还是相当方便的,要记住了:
        放入的对象必须是标准的pojo,不然的话.......你自已看看保存的 xml就知道了!

      
       最重要的部分,就应该是把生成的模型,与gef无缝集成了,不过涉及到的东西太多,思路倒是有,具体怎么做......会在下一章节,详细介绍的;

  • 11:18
  • 评论 / 浏览 (0 / 613)
用gmf实现业务模型编辑器
过去用Graphical Editor Framework(GEF)实现业务模型编辑器既慢又痛苦,光是理解复杂的GEF框架,就要花费很长时间,GEF是个典型的MVC框架,用户可以自定义模型,只要当模型的属性发生变化时,通知模型的监听器就可以了,GEF中模型的监听器被称为控制器,它在MVC框架中处于核心地位,是连接模型和视图的桥梁,它负责将模型的变化反映到视图上,同时把用户在视图上所作的操作反映到模型上,在这过程中还涉及到命令和策略等一些概念,这里我们就不细说,毕竟不是介绍GEF的。
当然我们可以借助于Eclipse Modeling Framework(EMF)来实现模型,因为用EMF生成的模型,就已经实现了消息通知机制,省去了自定义模型中很多工作量,如果EMF和GEF真能很好的结合,估计也不会有GMF的产生了,由于EMF和GEF采用不同的命令堆栈,给EMF和GEF要实现无缝结合带来了很大的技术问题,这个时候GMF就应运而生了(“时势造英雄”),它解决了GEF和EMF相结合中遇到的技术问题,当然了我并不是说有了Graphical Modeling Framework (GMF),GEF和EMF就没有用武之地了,深刻理解GEF和EMF对学习GMF有很大的帮助。
我们还是进入正题吧!这段时间由于工作的原因,对GMF作了一些研究,虽然只是了解了一些皮毛,但也想写出来和大家一起分享,同时也恳请这方面的大虾们给点意见。
下面,我就用GMF实现了一个简单的业务模型编辑器,以后可以在这基础上扩展。
我们知道GMF是以EMF建模为基础了,所以需要一个ecore模型,这里我们的ecore模型是从mdl转化过来的,我们用Rose设计一个mdl文件,如图:
在此模型中,有IPackage和IClass类(以后可以增加IAttribute,IAssociation类),它们之间是聚合关系。
构造ecore模型
1.打开Eclipse,在导航器视图右键,New-->Project,,新建一个GMF项目etm;
2.mdlàecore,在导航器视图右键,New-->Other,,新建一个EMF Model:etm.genmodel
 在Select Model Importer向导页中选择“Rose class model”,在Rose model Import向导页,选择我们前面新建的etm.mdl文件,点击完成,这样就产生了ecore模型和genmodel。
产生Model和Edit Code
1.       打开刚才产生genmodel,在根节点右键,点击Generate Model Code和Generate Model Code(产生的两个项目会被以后产生的业务模型编辑器项目引用)。
 创建其他模型
GMF有个非常实用的工具,如图:
它可以帮助你如何一步步地生成编辑器。
1.从Domain Model开始,选择select,在弹出的对话框中,选择我们前面产生的etm.ecore;
2.在Domain Gen Model,选择select,在弹出的对话框中,选择我们前面产生的etm.genmodel;
3.选择Domain Model上面的Derive,将弹出向导对话框,这个向导对话框用来产生Graphical Def Model,熟悉GEF的人就知道,这些Model对应图形化编辑器中的图形模型。在这个向导对话框第一个向导页可以指定产生Graphical Def Model的文件名和存放路径,这里我们接受默认值etm.gmfgraph,点“下一步”,在第二个向导页中,可以指定对应的ecore模型,这里我们接受默认值etm.ecore,点“下一步”,在第三个向导页中,只要指定Diagram Element为IPackage,即指定画布对应的模型,其它接受默认值,点完成即可。
4.选择Domain Model下面的Derive,将弹出向导对话框,这个向导对话框用来产生Graphical Tool Model,熟悉GEF的人就知道,这些Model对应编辑器调色板中的图形模型。在这个向导对话框第一个向导页可以指定产生Graphical Tool Model的文件名和存放路径,这里我们接受默认值etm.gmftool,点“下一步”,在第二个向导页中,可以指定对应的ecore模型,这里我们接受默认值etm.ecore,点“下一步”,在第三个向导页中,只要指定Diagram Element为IPackage,其它接受默认值,点完成即可。
5.选择Domain Model右边的Combine,将弹出向导对话框,这个向导对话框用来产生emt.ecore,etm.gmfgraph,etm.gmftool三者的Mapping Model。在这个向导对话框第一个向导页可以指定产生GMFMap Model的文件名和存放路径,这里我们接受默认值etm.gmfmap,点“下一步”,在第二个向导页中,可以指定对应的ecore模型,gmfgraph模型,gmftool模型,这里我们接受默认值,点“下一步”,在第三个向导页中,只要指定Diagram Root Element为IPackage,点“下一步”,在第四个向导页中,可以指定节点(Node)和连接弧(Link)对应的类,这里我们接受默认值,点完成即可。这儿我们还必须修改生成的etm.gmfmap文件,否则下面的操作就会出错。给Mapping\Top
 Node Reference\Node Mapping\Label Mapping的Diagram Label属性指定一个值,这里我们指定Diagram Label IElementName,即在节点图形上显示节点的name属性。
6选择Mapping Model右面的Transform,将弹出对话框,这里我们接受默认值etm.gmfgen,点完成,接下来,又弹出弹出对话框,点“Yes”即可。
7选择Diagram Gen Model里面的Generator Diagram Editor,就可以生成图形化编辑器了。
我们重新启动一个Eclipse 工作台,File\New\Examples\Etm Diagram如图:
  • 10:15
  • 评论 / 浏览 (0 / 961)

前言:本文源自Eclipse网站GMF子项目的同名实例文档。虽然本文绝大部分是原文的翻译,但是我仍然更愿意称其为“编译”而非“翻译”。原因是在读这个系列文档的同时,我也在学习GMF相关技术,而学习就会有些心得或想法以及时间操作中遇到的问题,所以本文的内容就不会仅局限于原文中所包含的内容。我会尽量将其中语焉不详或不够具体的部分加以细化,但也会对于某些我觉得冗长的部分也会予以简化,这种非完全“字字对译”的方法或许只能体现我的主观意愿,但我相信也不会离题太远。我记得有位著名的翻译家也说过,翻译其实也是一种再创作,如何将作者的本意用自己熟悉的语言表述出来是“字字对译”所不能达到的。再说原汁原味也未必适合我们中国人的习惯,要想吃原味大餐最好还是看原文。这个系列的教程一共有4个,也不知道我有没有时间翻译完,随遇而安吧,呵呵。

原文前言

本文旨在介绍图形化的模型框架[Graphical Modeling Framework (GMF)],该框架是一个Eclipse建模项目的子项目,其目标是为Eclipse建模框架[Eclipse Modeling Framework (EMF)]和图形化编辑框架[Graphical Editing Framework (GEF)]提供一个统一的桥梁。

在本文中,我们将开发一个称为mindmap的应用程序。本文所描述的有关GMF所提供的功能都是基于2.0M4版本的。随着GMF的不断演化,本文也会及时地更新内容以便可以覆盖GMF提供的最新功能。我们已经将例子工程的源码及其相关的内容打包,有需要的话可以到http://www.eclipse.org/gmf/tutorial/mindmap_tutorial_1.zip下载该示例工程。

GMF的系统需求

Eclipse3.3启动设置:我家的“老爷机”配置如下:P41.5/256M内存/40G硬盘。由于运行本文的例子需要以及网上的朋友都赞3.3版的内存使用率比以前有很大提高并且不容易出现在3.2版中不明不白死掉的状况,于是到官网下了一个,但只要一运行就会出现如下的对话框,然后Eclipse就自动终止:

忙活了好一阵之后发现,只要将eclipse.ini文件中的XX:MaxPermSize和Xmx改小一点就可以了,例如以我的机器来说只要设置成XX:MaxPermSize=80和Xmx=512,Eclipse即可正常启动。如果你用过3.2版的话,可以看到在那个版本中启动参数没有XX:MaxPermSize,并且-Xms和-Xmx分别是40M和256M,可见3.3版对内存的要求还是提高了。当然,如果你的机器有N个G内存的话,自然不会有任何烦恼,直接掠过这段就好J。

1.   GMF简介

目前,联合使用EMF和GEF来构建基于Eclipse的图形化应用程序已经越来越普遍。下面的许多参考资料都给出了如何将这两个框架整合使用的方法,其中的一些方法甚至需要深入到GMF工程内部。在进入GMF工程之前,让我们首先了解一下GMF是如何通过一个通用的方式来使用EMF和GEF来完成既定任务的。另一篇关于GMF 的runtime部分的文章可以在这里找到:http://www.eclipse.org/articles/Article-Introducing-GMF/article.html

GMF工程使用术语“toolsmith”来指代那些使用GMF来构建插件的开发者,而“user”通常指那些利用这些插件并同时也是开发者的人。在一个可用的透视图中,GMF以内部方式构建出来的“模型”的数量和种类应该在很大程度上是隐藏的。然而,大多数toolsmith都对台面之下正在发生什么感兴趣(注:好奇心重是天性,呵呵),所以在本文中我们将对每一个模型都给出一个详细的描述。

下图展示了在进行基于GMF应用开发时所需要使用的主要构件和模型。GMF的核心是一个概念模型(concept of a graphical definition model)。该模型包含了那些将会出现在基于GEF的runtime中,且与图形元素相关联的各种信息,而这些信息是与那些提供描述和编辑功能的领域模型(domain model)没有直接关联的。工具定义模型(tooling definition model)是一个可选组件,它可以用来设计palette(注:就是我们用VE时常常会看到的选择组件用的面板)以及其他实用组件(菜单,工具栏,等等)。

我们希望图形化或工具组件的定义可以在面对不同的领域时仍然能正常工作。例如,UML的类图有很多counterpart,而所有这些conterpart的基本外观和结构都是十分相似的。GMF的一个目标就是允许一个图形化定义可以被很多领域复用。通过使用一个独立的映射模型来将图形定义和工具定义连接到所选择的领域模型,GMF漂亮的完成了这一目标。

一旦定义了合适的映射,GMF就会提供一个生成器模型(generator model)来帮助用户确定在生成阶段(generation phase)需要定义的各种实现细节。一个基于生成器模型编辑器插件将会作为这一过程的产生品,并将导致最终的模型[final model]的产生。最终模型也称为图形运行时[diagram runtime](或“符号”)模型。当一个用户工作在一个图形上时,该runtime将会把符号和领域模型桥接起来,并同时提供持久化和序列化功能(注:以上提到的这几种模型都有实物对应,现在先不必细究)。这个runtime的一个很重要的方面是它提供了一个基于服务的方法来使用EMF和GEF,并且能通过non-generated式的应用程序来进行调整。

在了解了GMF的一些基本概念之后,接下来我们要将GMF应用到一个某一特殊领域的图形化编辑层的开发当中去。首先,你可能会需要安装GMF及其相关组件。

2.   GMF组件的安装

本文所用的GMF版本为GMF(2.0M4),请注意在build页上标明的GMF运行的先决条件,必须在将这些组件安装完之后才能安装GMF。安装方式很简单,或通过其下载页面,或通过更新管理器进行。

由于要安装的组件种类繁多,而且缺一不可,而且组件之间的安装也有一定的顺序,所以不推荐自己费时费力地一个一个到Eclipse的下载页面寻找。本文推荐的方法是利用Eclipse的更新管理器到Europa的更新站点进行自动升级,又轻松又安全。具体方法是,点击“Help->Updates->Find and Install….”,然后选“Serach for new features for install->Europa Discovery Site”。然后从“Models and Model Develop”列表中选择Graphical Modeling Framework(Europa Edition),然后点击“select Required”按钮来选择GMF所需要的相关组件。很简单吧?

但其实还有更简单的方法,在GMF的下载页面的中第一个连接是:Consolidated requirements zip,也就是说,开发人员已经把包打好了,该放的东西也放进去了,于是我们解压缩就可以直接用了。比起用上面的方法访问那个超慢的更新网站,这个方法显然更加方便。

除了上面所说的那个例子之外,各位还可以下载一个名为TaiPan的例子,它是使用最新版本的GMF构建而成,专门用来展示GMF最新功能的例子。所以,即便本文过时了,你也可以通过下载这个例子来获取最新的知识。不过这个例子需要使用CVS--速度超级慢,下载要有耐心才行。

2.1. TaiPan的下载与运行

如果你急切地想要看看GMF到底有什么值得你关注的地方,你就应该通过CVS下载Taipan示例工程到你的工作空间。否则的话,请直接阅读下一部分。

通过CVS下载资源之前,首先需要建立一个CVS Repository Location,具体方法是点击new->other,在CVS目录窗格下选择“CVS Repository Location”就会看到如下图的界面。

按照如下图所示的内容进行填写,完成后访问:/HEAD/org.eclipse.gmf/examples,并选择org.eclipse.gmf.examples.taipan.*模块(这里面好东西不少,希望多学一些的可以多下载些例子),右键点击并选择check out就可以下载了。如果你没有使用最新版本的GMF的先决组件,你总是可以通过检查与你正在使用的版本相对应的日期来下载对应版本的Taipan示例工程。很重要的一点是你必须使GMF SDK的版本与Taipan示例工程的版本保持一致才行。为了做到这一点,在校验完毕之后,你可以右键单击该工程,并选择“Team | Switch to Another Branch or Version...”,然后选择“Select the tag from the following list”并使用底部的“add Date…”按钮来输入GMF2.0M4 release的日期(03 January 2007)。最后点击Finish。

切换到插件开发透视图,并打开org.eclipse.gmf.examples.taipan工程下的model文件夹。查看每一个可用模型,特别是taipan.gmfgraph和taipan.gmfmap模型以及他们的元素属性。你会发现有很多rcp版本的Taipan例子可供学习。

在本文中,我们将会按顺序查看每一个模型,但仅仅是验证一下你的配置,你需要在一个运行时工作空间(runtime workspace)里自己运行这个例子(建立一个EclipseApplication运行配置,并简单的接收默认值即可)。在一个独立的工作空间中,建立一个空的工程和一个新的“TaiPan Diagram”(在一个新的运行时工作空间中,点击new机会发现建立TaiPan Diagram的选项),给他起个名字然后点击Finish。生成的图标编辑器会为你打开这个新生成的图形。学习时我们特别需要注意以下部分:

·   工具选项板和overview[tool palette and overview]

·   布局和选择工具[layout and selection tools]

·   图形图像的输出[diagram image export (svg, bmp, jpeg, gif) ]

·   层叠的属性视图[tabbed properties view]

·   选中元素的字体和颜色选项[font and color options for selected element]

·   连接路由和风格选项[link routing and style options]

·   弹出栏和连接柄[pop-up bars and connection handles]

·   短笺和几何图形[notes and geometric shapes]

·   即时缩放和布局[animated zoom and layout]

建立一个命名为TaiPanApplication新的启动配置,并在插件选项板中仅选择Taipan *.rcp插件及其依赖插件,然后以一个独立RCP图形编辑器的方式来运行Taipan。

以上是本文的一个概览,在下面的部分我会带领大家深入查看上面在谈到mindmap模型层所提及的每一个模型的细节。(编译者注:对于Taipan工程,如果有时间的话,我会在下一篇文章中详细介绍)

2.2. 一个新的工程

在我们开始新的工程之前,你需要先定制一下你的工作空间(或至少定制一下你的GMF工程)的编译器为JDK1.5编译器。具体位置在Window->Preference->Java->Compiler选项,然后选择支持JDK1.5的编译器。

一个典型的GMF的使用场景(usage scenarios)包括为mindmap应用程序产生一个图形层,最终补充进一个可供选择的、用于展示临时信息的视图(例如甘特图)。本节将会展示GMF在这方面的能力,并将随着工程的成熟而不断演化。如果你更喜欢使用那些包含了完整解决方案的工程的话,你可以通过用上面的方法通过查阅org.eclipse.gmf/examples模块来获取完整的工程。

GMF自带了一个“Tutorial Cheat Sheet”,你可以通过“Help | Cheat Sheets”来让它“显形”(主意,如果你已经将GMF安装到一个不含SDK的平台上,例如,使用Europa的更新管理器升级得到的GMF,你将需要从Window | Show View… | Other菜单打开Cheat Sheets的视图,然后从视图菜单中打开GMF Tutorial Cheat Sheet)。如果你打开了这个cheat sheet并遵照其提示的每一个步骤,你就能独立完成本文第一个阶段的大部分内容,并同时能获得一些以后需要的技能和知识。现在就试着建立你的新project吧。

作为可选项,你可以建立一个“New GMF Project”(使用Ctrl+N快捷键探出的窗口里找到“Graphical Modeling Framework”)作为学习的起点。将其命名为“org.eclipse.gmf.examples.mindmap”,并在工程根目录下建立一个名为“model”的新文件夹。

3.   领域模型的定义(Domain Model Definition)

虽然看起来以领域模型作为起点是必须的,但在GMF中却不是这样,因为图形的定义是与领域相分离的。然而和大多数教程一样,本文也将从领域模型开始,然后通过使用下一节要讲解的、由向导生成的领域模型来转入到我们的图形定义当中。在原文所给出的连接中,我们可以找到一个属于mindmap的ecore类型的基本领域模型。此外,你还可以使用mindmap模型的XSD版(在这种情况下,你可能需要安装EMF的XSD feature)。将这个文件复制到你的工程根目录下的‘model’文件夹,如果你愿意,可以随意进行一些你想要进行的测试或检查。作为标准EMF生成的编辑器的补充,GMF也提供了一个图形化的编辑器。并将其打包在SDK中。如果想用这个编辑器对mindmap.ecore(或任何 *.ecore模型)进行渲染(render),只需要简单地右键单击ecore文件,并在右键菜单中选择“Initialize ecore_diagram diagram file”即可。

接下来,使用New->Eclipse Modeling Framework > EMF Model wizard,从mindmap.ecore文件创建一个新的mindmap.genmodel。你也许会想要把genmodel的“base package”属性从“Mindmap”改成org.eclipse.emf.examples,以便使你所生成的包名与工程名保持一致。

通过右键单击生成器模型(generator model)的根所弹出的菜单,我们就可以生成模型并编辑代码了。虽然创建一个编辑器或进行测试不是必需的步骤,但是我建议各位还是实际操作一下。至此,我们已经为创建一个图形和有关mindmap应用程序的映射的定义(mapping definitions)作好了准备。

4.   图形的定义(Graphical Definition)

图形定义模型被用来定义形状,节点,链接(link)等等这些将会显示在你的图形理的元素。接着cheat sheet的下一步,我们将要创建一个新的图形定义模型。Cheat cheet将会启动一个简单的图形定义模型向导,我们同样也可以在“新建”对话框的GMF文件夹下找到它。在你的org.eclipse.gmf.example.mindmap工程下的‘model’文件夹中,选择mindmap.gmfgraph模型,在向导的下一页使用‘Browse’来定位你的mindmap.ecore文件。选择我们的Map类作为图形元素(diagram element)。

如下图所示,在向导的最后一页,为我们的Topic类选择一个元素,链接和标签选项的最小集合。稍后,你可以亲自验证一下这个向导并观察一下它的输出。现在,我们已经获得了启动应用程序所需模型的最小集合。点击finish来结束该向导。

提示:在GMF中包含了很多以复用为目的的图形库。通过使用“load resource1…”并输入“platform:/plugin/org.eclipse.gmf.graphdef/models/basic.gmfgraph”作为资源URL,你就可以将这些图形库载入到你的*.gmfgraph模型(或*.gmfmap模型)中。其他可用的资源包括classDiagram.gmfgraph和stateDiagram.gmfgraph。

5.   工具定义(Tooling Definition)

正如前面所描述的那样,工具定义模型被用来定义工具板,创建工具(creation tools),动作(actions)等等。Cheat sheet将会通过一个非常简单的过程来引导你掌握我们的这个简单的工具定义模型。事实上,这两个过程是十分类似的,因为mindmap领域模型是由其可能用到的工具所载入并检查的。我们将会简单地选择和图形定义模型相同的选项,为Topic名字的标签保存一个tool,然后为我们的Topic元素启动一个简单的palette。

通过提供给我们的模型,我们可以看到在一个Palette中存在着一个顶层“工具注册表(Tool Registry)”元素。Palette包含一个带有创建工具元素(Creation Tool element)的工具组,这些创建工具元素是专门为Topic节点和那些用向导定义的subtopic元素的链接而设立的。我们将会在未来对他们进行一些小改动,但是现在,我们将接受默认值并且将注意力转移到映射定义。你可以浏览一下这个模型并实地查看一下它们的各个属性来熟悉一下工具定义。

6.  

  • 09:41
  • 评论 / 浏览 (0 / 3260)

在GMF中当使用XYLayout时经常会遇到这样的问题:

1。在create一个新figure时,figure会跑到它的parent figure的外面。

2。在move或者resize时跑到parent figure的外面。

如下图logic example中的问题:

一般的情况下,用户会challenge这个现象,如果在resize的时候,即便把figure的边界拖出parent,figure可以自动保持在parent的内部就比较好。

做到这个也不难,重载XYLayoutEditPolicy中的public Object getConstraintFor(Rectangle r) 方法就行了。参数r是gmf xylayout默认返回的figure的constrain,你可以根据自己的需要改变r中的width, height, x,y等参数,具体的算法就因需求而异了。

看一下XYLayoutEditPolicy还有他的父类中有许多同名不同参数的getConstrainFor(...)方法,有可以重载来定制用户的resize或move figure的行为。就不多介绍了,看源代码吧!

  • 09:32
  • 评论 / 浏览 (0 / 566)
2008-06-19

GMF常见问题

    博客分类:
  • GMF
1、问题:连接线旁边没有文字标签和箭头

文字标签:在gmfmap里的Connection Mappping下增加Label Mapping元素;箭头:在gmfgraph里为Polyline Connection指定一个Polyline Decorator作为source/target decoration,要为这个Decorator创建一些Template Point来决定箭头的形状,例如指定(-1,-1), (0,0), (-1,1)。

2、让一个图形可以在另一个图形里随意改变位置

在gmfgen里把作为容器的那个图形的Gen Compartment里把Listlayout属性改为false。

3、隐藏图形标签文字前的小图标

在gmfgen里把相应的Gen Node Label元素的Element Icon属性改为false(但重新生成gmfgen时这个属性会被覆盖)

4、让标签里同时显示和编辑多个属性

在gmfmap里把相应的Label Mapping元素的View Pattern属性改为类似“属性1:{0},属性2:{1}”的形式。

5、问题:跨Compartment进行连线操作时会创建两条连线

GMF的bug,见https://bugs.eclipse.org/bugs/show_bug.cgi?id=148021,在你的XXXDiagramCanonicalEditPolicy里覆盖方法:

protected boolean shouldHandleNotificationEvent(Notification event) {
    return false;
}

6、让Label出现在图元外面

在gmfgraph里定义这个Figure时把Label定义在外面,而非定义为Figure的子元素。

7、在gmfgraph里设置一个Figure使用GridLayout后生成的代码无法正确编译

GMF的bug,见https://bugs.eclipse.org/bugs/show_bug.cgi?id=133279

8、改变Figure的缺省大小

在gmfgraph里为Figure增加Preferred Size子元素;若想让图形尺寸小于40x40象素,要覆盖XXXEditPart里的createNodePlate()方法。在GMF2.0里,使用DefaultSizeFacet,见http://dev.eclipse.org/newslists/news.eclipse.modeling.gmf/msg01546.html

9、禁止用户修改图元的尺寸

在gmfgraph里将此Node的Resize Constraint属性值改为“NONE”(但size-on-drop功能仍存在,也就是用户仍然可以在创建时指定尺寸)。

10、让Compartment在容纳不下子图形时自动显示滚动箭头

在genmodel的GenDiagram元素里改Units属性为“himetric”(经测试对GMF1.0不起作用),见https://bugs.eclipse.org/bugs/show_bug.cgi?id=140789

11、为画布Canvas指定Layout

GMF1.0不支持,需要手工改代码,见https://bugs.eclipse.org/bugs/show_bug.cgi?id=139951

12、Border Item

Border Item是指只能紧贴其他图元运动的图形,GMF1.0可通过打patch实现这个功能,见https://bugs.eclipse.org/bugs/show_bug.cgi?id=124826;GMF2.0开始支持。

13、规定连接线的约束,例如规定source和target不能是同一对象

在gmfmap里定义,在Link Mapping元素下定义Link Constraint元素,缺省使用OCL,见教程http://wiki.eclipse.org/index.php /GMF_Tutorial_Part_2#Link_Constraints;不论使用OCL或是Java,在 XXXBaseItemSemanticEditPolicy里会生成LinkConstraint类,在生成command前检查是否满足这些 constraint。

14、Audit

定义的constraint出现在com.your.diagram项目的plugin.xml里,作为constraintProvider扩展;为了让这些constraint生效,要在gmfgen的Gen Diagram元素里设定Validate Enabled/Decorator属性值为true,并将优先级(Validation Provider Priority, Validation Decorator Provider Priority)设定为medium(非lowest)才会在Diagram菜单里出现Validate命令。

若是在gmfmap里选择使用Java验证,则在gmfmap里指定的是一个Java方法名,生成代码后,应在XXXValidationProvider类里应实现这个方法。

15、GMF里从EditPart得到Semantic Model

因为GMF里EditPart#getModel()方法得到的是Notation Model里的对象,如Node或Edge,所以可以使用这样的方法得到真正的业务对象: ((org.eclipse.gmf.runtime.notation.View) EditPart.getModel()).getElement()或ViewUtil.resolveSemanticElement(view)

16、问题:从gmfgen生成代码时产生java.lang.ClassCastException: org.eclipse.jdt.internal.core.jdom.DOMMethod

删除原先生成的代码中无法编译的类,重新生成。

17、在gmfgraph里定义Polyline的图形

在Rectangle上画Polyline,注意是固定大小的

18、问题:每次重新生成代码后,在plugin.xml里的修改会丢失

GMF1.0里生成代码时不能保留plugin.xml里的任何修改,从GMF2.0开始用户可以在plugin.xml里标记不要覆盖的区域

19、问题:Outline树视图里的节点没有图标

在plugin.xml里找到org.eclipse.gmf.runtime.emf.type.core.elementTypes扩展点,在下面相应的元素里指定icon属性,见http://dev.eclipse.org/newslists/news.eclipse.modeling.gmf/msg00341.html,但我在GMF1.0里测试不起作用,何况每次生成代码时这个文件都会被覆盖。

20、问题:提示java.lang.IllegalStateException: Cannot modify resource set without a write transaction异常

在GMF里修改Model要通过在TransactionalEditingDomain里执行命令完成,GMF提供的RecordingCommand是不错的选择,它为我们提供了Undo支持,我们只要实现执行部分的代码就可以了,下面是一个例子:

TransactionalEditingDomain domain = TransactionUtil.getEditingDomain(myElement);
domain.getCommandStack().execute(new RecordingCommand(domain) {
    @Override
    protected void doExecute() {
        //Do anything
    }
});

若是在EditPolicy里需要返回一个Command,用下面的代码:

AbstractTransactionalCommand command = new AbstractTransactionalCommand(TransactionUtil
        .getEditingDomain(myElement), "Command Name", Collections.EMPTY_LIST) {
    @Override
    protected CommandResult doExecuteWithResult(IProgressMonitor monitor, IAdaptable info)
            throws ExecutionException {
        //Any modification to the model
        return CommandResult.newOKCommandResult();
    }
};

21、问题:创建图元时提示异常“java.lang.IllegalArgumentException: Figure must be a child”

Workaround:注释掉产生异常的setRatio()方法里的全部内容。

22、问题:在AbstractBorderItemEditPart子类的getPrimaryDragEditPolicy()方法里提示ClassCastException异常

在gmfgraph里检查作为BorderItem的那个Node的Resize Constraint属性是否改过,若为缺省的NSEW则对应的editpart不会生成这个方法,对BorderItem(即Affixed Node Side属性不为NONE的Node)来说这个属性虽然设置为NSEW也无法改变大小。相关链接:https://bugs.eclipse.org/bugs/show_bug.cgi?id=155698

23、如何禁用PopupBar和ConnectionHandler功能(鼠标停止在图形上时出现的连线符号)

在需要禁用该功能的EditPart的createDefaultEditPolicies()方法的最后加下面的语句:

//禁用PopupBar
removeEditPolicy(EditPolicyRoles.POPUPBAR_ROLE);
//禁用ConnectionHandler
removeEditPolicy(EditPolicyRoles.CONNECTION_HANDLES_ROLE);

24、使用ConnectionHandler连接到canvas上已存在的图形或创建新的图形

覆盖XXXModelingAssistantProvider里的几个get方法,要连接到已存在的图形覆盖 getRelTypesOnSourceAndTarget()方法,创建新的作为源的图形覆盖getRelTypesOnSource()和 getTypesForTarget()方法,创建新的作为目标的图形应覆盖getRelTypesOnTarget()和 getTypesForSource()方法。具体代码可参考LogicModelingAssistantProvider里的实现。

25、给画布加背景图

http://www.cnblogs.com/bjzhanghao/archive/2007/03/13/673273.html
BTW, 以上所有问题只针对GMF1.0,GMF2.0的gmfmap模型和gmfgen模型与前一版本有所不同,一些问题可能也在GMF2.0里不存在了。

26、使用Label作为一个editpart的figure

在.gmfgraph里不用创建Node,只用Diagram Label即可;在.gmfmap里,Node Mapping的Diagram Node属性指定为这个Diagram Label,下面的Feature Label Mapping的Diagram Label属性也是这个Diagram Label。在parent使用ListLayout的时候这个方法比较有用。GMF的mindmap例子里的ThreadItem就是这样一个例子。

27、若类A包含B和C,且C继承B,则试图让A的图形同时包含B和C会造成运行时异常,异常信息是无法创建C的View,可能是GMF目前版本的bug。解决办法是建立抽象类D,让B和C都继承D,并且让A包含D。(update 2007/7/23: 有一点像这个bug,异常信息差不多)

28、用渐变色填充非矩形图形

覆盖图形的fillShape()方法,利用swt的Path,但draw2d的graphics对它的支持似乎不好。http://dev.eclipse.org/newslists/news.eclipse.tools.gef/msg13928.html

29、(这条实际是关于EMF的,anyway)为TableViewer增加Drag and Drop支持

非常简单,见下面的代码(tv是TreeViewer的一个实例)

int dndOperations = DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_LINK;
Transfer[] transfers = new Transfer[] { LocalTransfer.getInstance() };
tv.addDragSupport(dndOperations, transfers, new ViewerDragAdapter(tv));
tv.addDropSupport(dndOperations, transfers, new EditingDomainViewerDropAdapter(editingDomain, tv));

30、从EObject得到TransactionalEditingDomain

TransactionalEditingDomain editingDomain = TransactionUtil.getEditingDomain(eobject);

31、让Label换行

.gmfgraph里无法指定Label是否换行,要修改生成的代码:

fFigureXXXFigure = new WrapLabel();
fFigureXXXFigure.setTextWrap(true);//add this line
fFigureXXXFigure.setText("<>");

另外可以在.gmfgraph里指定需要的布局以便让换行Label更好的显示。给Label设置Margin Border会有问题(Label被推向右侧),可以给Parent图形设置Margin Border,或建一个RectangleFigure来实现设置文字边距的需求。

32、定制Project Exploerer里显示的内容

修改.gmfgen里Gen Navigator节点下面的元素,见http://wiki.eclipse.org/index.php/GMF_Tutorial_Part_4#Project_Navigator

33、可缩放的多边形

在.gmfgraph里定义为Scalable Polygon,和普通Polygon一样要定义template points,每个点的坐标绝对值不是关键,但它们之间的位置关系要保证。我发现绝对值定义得大一些时,得到的结果会更精确。下面是一个可缩放菱形的定义:
<descriptors name="ConditionFigure">
 
  <actualFigure xsi:type="gmfgraph:ScalablePolygon" name="MyDiamondFigure">
    <template x="200" y="0"/>
    <template x="0" y="200"/>
    <template x="200" y="400"/>
    <template x="400" y="200"/>
    <template x="200" y="0"/>
  </actualFigure>
</descriptors>

34、生成的RCP应用里,保存操作后经常提示“the file has been modifying on the file system...”信息。

GMF太“聪明”了,每次save后都要记录文件修改的timestamp,一旦发现不符则认为有其他程序修改了这个文件。要让RCP应用不检查当前编辑的文件是否被其他程序修改,可覆盖XXXDocumentProvider的isSynchronized()方法,让它直接“return super.isSynchronized(element);”。(但要小心,这有可能造成用户的修改无法被保存的情况。)

35、生成的GMF应用程序里,打印功能是禁用状态。
打开.gmfgen文件,修改Gen Plugin的"Printing Enabled"属性为true,再重新生成代码。这样除了 Print变为可用外,GMF还会生成一个XXXContributionItemProvider类在主菜单上添加Print Preview选项。 http://dev.eclipse.org/newslists/news.eclipse.modeling.gmf/msg02207.html

36、(实际是Eclipse OCL问题)脱离Eclipse环境使用OCL时,报异常java.lang.NoClassDefFoundError: lpg/lpgjavaruntime/RuleAction

Eclipse OCL依赖lpg库(LALR parser generator,使用EPL协议),在RCP里使用OCL需要把net.sourceforge.lpg.lpgjavaruntime这个插件加在dependencies列表里。参考链接

37、(还是OCL问题)Eclipse OCL实现里,OCL语句里各集合类型与ecore里集合类型的映射:
Collection Type isUnique isOrdered
Bag N N
Sequence N Y
Set Y N
OrderedSet Y Y

所以,如果一个EList在ecore里定义为Unique且Ordered(即缺省定义)时,在OCL里应该用OrderedSet类型,例如:XXX->allInstances()->asOrderedSet()或OrderedSet{object1, object2},等等。

38、在画布上创建一个元素(包括连接)后根据当前模型状态自动设置某属性值:

(GMF允许通过多种语言如ocl、regexp和java来实现初始值的设置,这里以java为例)在xxx.gmfmap文件里,找到这个元素对应的Mapping节点(如Node Mapping或Link Mapping),点右键新建一个Feature Seq Initializer元素,在这个元素上点右键再新建一个Feature Value Spec元素,设置后者的Feature为想要设置的类型,语言选java,在Body属性里输入一个方法名,例如 “initialMyFeature”。重新生成.gmfgen和代码,GMF会在名为ElementInitializers.java的文件里生成 initialMyFeature()这个空壳方法,实现它即可。

39、新建向导结束后,生成一个非空的模型文件。(Customize新创建的模型文件)

修改XXXDiagramEditorUtil#createInitialModel()方法。

40、在partition diagram里,从shortcut到一个正常节点间的连线在关闭editor后再次打开时丢失(2008.1.4)

原因不明,暂时的解决方法是注释掉XXXCanonicalEditPolicy#refreshConnections()方法里的deleteViews(existingLinks.iterator()),其中XXX代表link元素的父元素,例如Diagram。(这个解决方法有严重问题,会造成Initialize Diagram时丢失全部连接)。新探索出来的解决方法如下,覆盖XXXCanonicalEditPolicy#sholdDeleteView()方法:
/** *//**
* @generated NOT
*/
protected boolean shouldDeleteView(View view) {
    if(view instanceof Edge){
        Edge edge = (Edge)view;
        View sourceView = edge.getSource();
        View targetView = edge.getTarget();
        if(sourceView.getEAnnotation("Shortcut")!=null
                || targetView.getEAnnotation("Shortcut")!=null){
            return false;
        }
    }
    return true;
}

41、删除右键菜单里不需要的菜单项

在plugin.xml里声明contributionItemProviders扩展点,在popupContribution下指定如下元素:
<popupPredefinedItem id="autoSizeAction"remove="true"/>

一些GMF Runtime定义的ID:deleteFromModelAction, navigateGroup, fileMenu, toolbarArrangeAllAction, addGeoShapesGroup, addGeoShapes2Group

详见org.eclipse.gmf.runtime.diagram.ui.actions.ActionIds

42、在单独的编辑窗口里编辑子图(Diagram Partitioning)

http://wiki.eclipse.org/Diagram_Partitioning

43、在单独的项目(非GMF生成的xxx.diagram项目)里扩展DiagramEditor能识别的adapter类型:

在单独项目的Activator的start()方法里用类似下面的代码,这样就不需要直接修改生成的XXXDiagramEditor#getAdapter()方法了(因为我们希望把定制的内容尽量放在生成的项目以外):
//Register adapters for ERM reports and charts
Platform.getAdapterManager().registerAdapters(new IAdapterFactory() {

    /** *//**
     * @see org.eclipse.core.runtime.IAdapterFactory
     */
    public Object getAdapter(Object adaptableObject, Class adapterType) {
        ErmDiagramEditor editor = (ErmDiagramEditor) adaptableObject;
        Process process = (Process) editor.getDiagram().getElement();
        if (adapterType == IRiskImportancePage.class) {
            return new RiskImportancePage(process);
        } else if (adapterType == IRiskDrillDownPage.class) {
            return new RiskDrillDownPage(process);
        }

        return null;
    }

    /** *//**
     * @see org.eclipse.core.runtime.IAdapterFactory
     */
    public Class[] getAdapterList() {
        return new Class[] { IRiskImportancePage.class, IRiskDrillDownPage.class };
    }

}, ErmDiagramEditor.class);

猜您喜欢

备案号:苏ICP备12016861号-4