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还提供了十分丰富的扩展性,便于开发适合特定需求的应用程序。

file

图表 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)属性。

file

图表 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生成的编辑器比起来够不够“精装修”的标准:

file

图表 3-1 GMF生成的流程编辑器

file

图表 3-2 EMF缺省编辑器

当然,这个编辑器的外观可能还不能让你满意,比如除了连接线外所有的图元都是用矩形表示的,未免过于单调,而且每个图元上只显示了有限的文字信息。下面我们介绍一下如何通过修改GMF的几个模型来定制流程编辑器。

首先,要改变各个图形元素的缺省外观,在缺省情况下,GMF建模向导里都将节点元素映射为矩形(Rectangle),连接元素映射为折线(Polyline),当然这些都可以在gmfgraph里修改,例如我们可以让开始和结束节点显示为圆点,让条件节点显示为菱形,将活动节点填充为蓝色,同时可以为连接线的目标端增加箭头图形来表示连接的方向,这样一来,熟悉UML的用户就更容易理解流程图的含义了。

从上面不难看出,gmfgraph模型的作用是定义图形表现的方式,GMF映射模型负责关联业务模型、gmfgraph模型和gmftool模型。任何在gmfgraph里定义的元素如果没有映射到业务模型也是没有意义的,相对来讲gmfmap模型是这三个模型中最复杂的一个,因为几乎每个元素都至少有三个属性需要定义。通过修改gmfmap模型,我们可以规定图形编辑器里,各图形元素的包含关系。在流程编辑器里,可以通过添加一个标签映射(Label Mapping)元素让活动节点里显示优先级属性。

file

图表 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增加的新功能我们拭目以待。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/01/16/621545.html

将SWT包装成Eclipse Plugin需要修改的地方

把一个SWT程序包装到Eclipse里作为Plugin运行很容易,但有以下几点需要注意:

1、Eclipse的PDE Tools->Convert Projects to Plug-in Projects可生成Plugin工程需要的文件,注意原来在classpath里对swt.jar的引用应改为manifest.mf文件里对 org.eclipse.ui的依赖。

2、相对路径文件的使用,在SWT里用ClassLoader.getResourceAsStream()等方法引用的文件放在Plugin里会找不到,应改为

FileLocator.toFileURL(Platform.getBundle("plugin.id").getEntry("/images")).getFile();

或与其等效的方式。

3、对本地方法需要的静态链接库dll文件的引用,如果SWT程序是在启动参数里指定的,在Plugin里需要修改,可在系统环境变量里加到Path里。

4、SWT程序的入口类一般会被Editor或View等Plugin元素代替,要注意原来在入口类里初始化的变量也要改到Plugin里,特别是静态变量,正确初始化以防止NullPointerException。

5、必要的话,利用JFace等Plugin特性改写原来的SWT对话框、树、表格等元素,可使UI更具Plugin风格。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/01/11/617449.html

[Eclipse]在程序里隐藏但利用Resource Navigator

有些时候需要在应用程序里隐藏Resource Navigator,用程序控制而非双击鼠标打开编辑器。一个方法是让程序在目标workspace里创建一个project和需要的文件,用户不知道它们的存在:

IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IProject project = root.getProject(".my");
if (!project.exists()) {
    try {
        project.create(null);
    } catch (CoreException e1) {
        e1.printStackTrace();
    }
}
if(!project.isOpen()){
    try {
        project.open(null);
    } catch (CoreException e1) {
        e1.printStackTrace();
    }
}
IFile file = project.getFile("default.my");
if (!file.exists()) {
    InputStream is = this.getClass().getResourceAsStream("/data/blank.my");
    try {
        file.create(is, false, null);
    } catch (CoreException e1) {
        e1.printStackTrace();
    }
}
FileEditorInput input = new FileEditorInput(file);
openEditor(input, MyEditor.EDITOR_ID);//IWorkbenchPage#openEditor()

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/01/11/617260.html

Eclipse里几个常用视图的ID

Eclipse提供的几个常用公共视图的ID,在定义perspective时可能会用到,如下:

//Resource Navigator
public final static String VIEW_ID_NAVIGATOR = "org.eclipse.ui.views.ResourceNavigator";

//Properties
public final static String VIEW_ID_PROPERTIES = "org.eclipse.ui.views.PropertySheet";

//Outline
public final static String VIEW_ID_OUTLINE = "org.eclipse.ui.views.ContentOutline";

Update(2007/4/9): Eclipse提供的IPageLayout接口里已经包含了这些ID,直接使用更方便,例如IPageLayout.ID_OUTLINE

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2007/01/10/616998.html

改变GMF应用程序画布的布局

在GMF帮我们生成的应用程序里,画布(Canvas)的布局缺省是使用XYLayout的,这种布局适合UML类图、流程图等应用,但并不适合序列图或表格这类应用。而且目前GMF的.gmfgraph模型里没有提供为整个Canvas设置布局的地方,因此只能通过修改代码的方式实现。因为GMF Runtime是基于GEF的,即GMF生成的代码本质上也是一个GEF应用程序,所以根据以往编写GEF应用程序的经验,不难找到需要修改的地方。

首先要修改作为画布的那个EditPart(下称yourCanvasEditPart),这个类是从gmf的DiagramEditPart继承过来的,看一下后者的createDefaultEditPolicies()方法就会发现它的LAYOUT_ROLE的EditPolicy是gmf的XYLayoutEditPolicy:

installEditPolicy(EditPolicy.LAYOUT_ROLE, new XYLayoutEditPolicy());

GEF里同一个EditPart上要覆盖一个已有EditPolicy的方法是使用同样的Role安装新的EditPolicy,所以我们应该在yourCanvasEditPart的createDefaultEditPolicies()方法里使用LAYOUT_ROLE安装自己的EditPolicy,这里为方便使用了GEF提供的FlowLayoutEditPolicy,你若有特殊需要可实现自己的EditPolicy:

installEditPolicy(EditPolicy.LAYOUT_ROLE, new FlowLayoutEditPolicy() {

    @Override
    protected Command getCreateCommand(CreateRequest request) {
        return null;
    }

    @Override
    protected Command createMoveChildCommand(EditPart child,
            EditPart after) {
        return null;
    }

    @Override
    protected Command createAddCommand(EditPart child, EditPart after) {
        return null;
    }

    @Override
    protected boolean isHorizontal() {
        return true;
    }

});

我们知道,GEF里每个EditPart上作为LAYOUT_ROLE安装的EditPolicy应该和此EditPart对应的Figure使用的LayoutManager匹配,例如XYLayoutEditPolicy对应XYLayout,而FlowLayoutEditPolicy匹配的LayoutManager就是FlowLayout或ToolbarLayout,这两个LayoutManager最大的区别是前者允许子图形换行(列)排列,而后者只能有一行(列)。要注意一点,FlowLayoutEditPolicy与ToolbarLayout一起使用存在一点问题,原因是在FlowLayoutEditPolicy的isHorizontal()方法里认为传入的layoutmanager参数是FlowLayout,因此我们在上面代码里覆盖了isHorizontal()方法,至于返回true或false根据你的需求决定。

现在,为了匹配修改后使用的FlowLayoutEditPolicy,我们要覆盖yourCanvasEditPart的createFigure()方法,在父类DiagramEditPart里这个方法返回的Figure使用的是FreeFormLayout,我们现在需要的是ToolbarLayout,所以:

protected IFigure createFigure() {
    Figure f = new FreeformLayer();
    ToolbarLayout layout = new ToolbarLayout();
    layout.setVertical(false);
    layout.setSpacing(10);
    layout.setStretchMinorAxis(true);
    f.setLayoutManager(layout);
    f.setBorder(new MarginBorder(15));
    return f;
}

经过这样两处修改后,你的编辑器应该已经像下图所示这样了(可能还有些细节代码需要处理一下):

file

GMF在GEF基础上做了很多包装,例如添加了SemanticEditPolicy、CanonicalEditPolicy等等新的EditPolicy,以及相关的很多Command和EditPart,它们为GMF应用程序提供了新的功能,虽然掌握GMF Runtime的API需要花些时间,但这允许我们在不进行大规模编写代码的情况下获得符合需求的图形化编辑器,建议先提前了解一下。

Update(2007/07/17): 在GMF 2.0里,可以通过设置xxx.gmfgen文件里Figure Viewmap的"Layout Type"属性为"TOOLBAR_LAYOUT"达到同样目的。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/12/25/603074.html

EMF里EReference的containment和container属性

EReference的containment属性如果为true,表示目标EClass是被源EClass包含的,这是一种十分强的关系,例如汽车和车轮的关系。在ecore里,不允许包含关系形成圈,也就是说如果A包含B,B就不能再直接或间接的包含A;另外,如果作为容器的对象结束了自己的生命周期,被它包含的对象也将结束自己的生命周期。

如果一个EReference有作为EOpposite的EReference,并且后者的containment属性为true,则这个EReference的container属性为true。换句话说,一个EReference的container属性表示它指向的EClass是否为包含者。

public boolean isContainer()
{
  EReference theOpposite = getEOpposite();
  return theOpposite != null && theOpposite.isContainment();
}

最后,container属性是derived属性,所以在ecore编辑器里我们无法直接编辑一个EReference的这个属性。

参考资料

http://dev.eclipse.org/newslists/news.eclipse.tools.emf/msg01448.html

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/11/20/566556.html

[Eclipse]RCP程序的启动进度条

在RCP应用的.product文件里指定Add a progress bar和进度条的尺寸以后,进度条很可能并未如你设想中出现在Splash图片上,解决的方法如下:

1、在.product文件同一目录里创建customization.ini文件,包含这样的文本内容:

org.eclipse.ui/SHOW_PROGRESS_ON_STARTUP=true

2、在plugin.xml里的org.eclipse.core.runtime.products扩展点下的对应product下添加属性preferenceCustomization,值为customization.ini;这个扩展点一般是在编辑.product文件后导出RCP时自动生成的。

3、为让所有的修改生效,可能需要以-clean方式启动你的RCP程序。

4、进度条的位置和大小也可直接在plugin.xml里指定,同样是对应product下的属性,分别为startupProgressRect和startupMessageRect,属性值例如10,290,200,16,前两个数字是位置,后两个是尺寸。

file

BTW,在3.3版本的Eclipse里可能会免去在customization.ini文件里做设置的步骤。

补充(2009.8.20):控制perspective切换按钮位置用org.eclipse.ui/DOCK_PERSPECTIVE_BAR = topRight

参考资料

http://www.eclipsezone.com/eclipse/forums/m92055175.html
http://eclipsezone.com/eclipse/forums/m92053781.html

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/10/31/545974.html

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,异常信息差不多)

update(2008/10/09): 今天再次遇到了这个问题,复制异常信息如下以便查找:

org.eclipse.core.commands.ExecutionException: While executing the operation, an exception occurred
    at org.eclipse.core.commands.operations.DefaultOperationHistory.execute(DefaultOperationHistory.java:519)
...
Caused by: org.eclipse.core.runtime.AssertionFailedException: null argument:failed to create a view
    at org.eclipse.core.runtime.Assert.isNotNull(Assert.java:86)
    at org.eclipse.gmf.runtime.diagram.ui.commands.CreateCommand.doExecuteWithResult(CreateCommand.java:99)

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。

Update: 上面的方法有严重问题,会造成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);
  1. 允许创建shortcut

在.gmfgen的Gen Diagram元素的属性“Shortcuts Provided”和"Contains Shortcuts To"里设置相应的model名字,然后重新生成代码,这样画布的右键菜单里将出现“Create Shortcut...”菜单项。参考http://wiki.eclipse.org/GMF_Tutorial_Part_2#Shortcuts

  1. 提示“Cannot activate read/write transaction in read-only transaction context”

在editpolicy的getXXXCommand()里不能直接对模型进行操作,否则将提示上面的异常,应该返回一个ICommandProxy(yourCommand) ,其中yourCommand一般继承自AbstractTransactionalCommand。参考http://dev.eclipse.org/mhonarc/newsLists/news.eclipse.modeling.gmf/msg04366.html

  1. (实际是OCL问题)在ocl语句里增加预定义的变量,当变量是集合类型时,如何setType:
OCL<?, EClassifier, ?, ?, ?, ?, ?, ?, ?, Constraint, EClass, EObject> ocl;
ocl = OCL.newInstance(EcoreEnvironmentFactory.INSTANCE);

//Customize this OCL environment
Variable appContextVar = ExpressionsFactory.eINSTANCE.createVariable();
appContextVar.setName("customers");
EClassifier type = TypeUtil.resolveSequenceType(ocl.getEnvironment(), EcorePackage.Literals.ORDERED_SET_TYPE);
appContextVar.setType(type);
ocl.getEnvironment().addElement(appContextVar.getName(), appContextVar, true);

try {
    OCLHelper<EClassifier, ?, ?, Constraint> helper = ocl.createOCLHelper();
    helper.setContext(SpmsPackage.Literals.SP_SERVICE_PLAN);
    OCLExpression<EClassifier> exp = helper.createQuery(metric.getFormula());//Seems this call is time costly
    Query<EClassifier, EClass, EObject> query = ocl.createQuery(exp);
    query.getEvaluationEnvironment().add(appContextVar.getName(),
            SpmsResourceManager.getInstance().getCustomerModel().getCustomers());
    Object object = query.evaluate(servicePlan);
    System.out.println(object);
    if (object instanceof Number) {
        result = ((Number) object).doubleValue();
    }
} catch (ParserException e) {
    e.printStackTrace();
}
ocl.dispose();

47、创建一个新的Diagram实例

GMF生成代码时同时生成了工具类XXXDiagramEditorUtil,调用XXXDiagramEditorUtil.createDiagram()即可创建新的Diagram实例。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/10/26/541262.html

[Eclipse]让多个Viewer对应一个属性视图

为了让用户在WorkbenchPart(如IEditorPart)的Viewer(如TableViewer)里选择一个对象时,属性视图里能显示这个元素的属性,一般会将这个Viewer设置为该WorkbenchPart的site的selectionProvider:

editPart.getSite().setSelectionProvider(theViewer);

但如果是一个WorkbenchPart里有多个Viewer的情况呢?我们可以实现为当用户在任意一个Viewer里选择了一个对象后,属性视图里显示这个对象的属性。为此,根据黄老大的提示,需要写一个CompositeSelectionProvider,它实现ISelectionChangedListener以监听每个Viewer的选择事件,同时它要实现ISelectionProvider,因为我们要把它设置为该site的selectionProvider:

class CompositeSelectionProvider implements ISelectionProvider, ISelectionChangedListener {
    ISelection selection;

    List listeners = new ArrayList();

    public void addSelectionChangedListener(ISelectionChangedListener listener) {
        listeners.add(listener);
    }

    public ISelection getSelection() {
        return selection;
    }

    public void removeSelectionChangedListener(ISelectionChangedListener listener) {
        listeners.remove(listener);
    }

    public void setSelection(ISelection selection) {
        this.selection = selection;
        final SelectionChangedEvent e = new SelectionChangedEvent(this, selection);
        Object[] listenersArray = listeners.toArray();

        for (int i = 0; i < listenersArray.length; i++) {
            final ISelectionChangedListener l = (ISelectionChangedListener) listenersArray[i];
            Platform.run(new SafeRunnable() {
                public void run() {
                    l.selectionChanged(e);
                }
            });
        }
    }

    /*****************************************************************
    ************** ISelectionChangedListener implementation **********
    ******************************************************************/
    public void selectionChanged(SelectionChangedEvent event) {
        setSelection(event.getSelection());

    }

}

在WorkbenchPart#createPartControl()方法里让这个CompositeSelectionProvider监听每个viewer的选择事件,并把它设置为site的selectionProvider,代码如下:

//The composite selectionProvider instance
CompositeSelectionProvider compositeSelectionProvider=new CompositeSelectionProvider();

public void createPartControl(Composite parent) {
    ...
    viewer1.addSelectionChangedListener(compositeSelectionProvider);
    viewer2.addSelectionChangedListener(compositeSelectionProvider);
    ...
    getSite().setSelectionProvider(compositeSelectionProvider);
}

 注:在这篇教程里也有类似的解决方法,见“Multiple Selection Providers Within a Part”一节。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/10/23/537205.html

[Eclipse]让输出的Plug-in文件名里包含当前时间

在MANIFEST.MF里指定的Bundle-Version时一般是1.0.0的形式,这样输出的Plug-in文件会是com.my.plugin_1.0.0.jar

其实还可以用qualifier关键字让Eclipse在输出这个Plug-in时在文件名里增加输出时的时间,精确到分。例如指定Bundle-Version为1.0.0.qualifier,则输出的文件名就是com.my.plugin_1.0.0.200609131335.jar,这个技巧在开发测试时比较有用,从文件名就能分辨Plug-in的新旧。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2006/09/12/502006.html