GEF 入门手册


duduwoo 1 第 1 回 GEF 版本的 HelloWorld 本回要点: z RCP 项目建立 z 显示 RCP 中的 Editor z GEF 的基本结构 前提 这里要讲的 GEF 例子,还是从任何学习编程的最普通例子 HelloWorld 开始。我们要用 GEF 这把牛刀 来处理 HelloWorld 这个小菜。下表是我所用的 Windows XP 下编程环境。对 Windows 2000 用户而言,用 Eclipse 建立 RCP 工程时可能会有错误,这是我一个哥们发现的,解决的办法就是……..这里先卖个关子, 下文再说。 注意 JDK 最好用 1.5 以上的,否则用 EMF 处理 XML 模型的时候就会出问题了。还是直接用 JDK1.5 比较方便解决这个问题。 Java 包 Version JDK 1.5 Eclipse 3.1.0+ GEF 3.1.0+ Draw2D(包含在 GEF 中) 3.1.0+ EMF 2.1.0+ 建立一个 RCP 工程 GEF 可以建立在 Eclipse 的 View 中,不过最普通的是建立在 Editor 中,我想这是因为 Editor 提供了文 件的保存机制吧。其实 View 和 Editor 的区别很难讲,有兴趣的可以看看 RCP 那本书。 我们这里的教程就是把 GEF 建立在 RCP 之上。所以我们要先建立一个 RCP 工程。 z 首先选择 Plug-in Project carolwoo@gmail.com duduwoo 2 z Project name 为 gef.tutorial.step z 在 Rich Client Application 中选择 Yes carolwoo@gmail.com duduwoo 3 z 选择 Hello RCP,单击 Finish 结束。 carolwoo@gmail.com duduwoo 4 设置 plug-in 工程的 Dependability 为了使用 GEF,我们需要给这个工程加上 org.eclipse.gef (3.1.0)。打开 plugin.xml 文件,在 dependencies 页 面中单击 Add…找到 org.eclipse.gef (3.1.0),OK 后就加上了。 创建 Editor 下面该创建最重要的部分 Editor 了。最通用的例子程序是把 GEF 的图形画在 Editor 上,我不明白为什 么有些人很希望把图画到View 上。其实Editor从EditorPart派生而来,提供了对图形修改后的提示保存(dirty 处理)和保存等函数。关于 Editor 和 View 的介绍和区别,可以参考这两本书: z Eclipse Rich Client Platform : Designing, Coding, and Packaging Java(TM) Applications (Eclipse (Addison-Wesley)) z Eclipse: Building Commercial-Quality Plug-ins (Eclipse Series) (1) (2) (3) carolwoo@gmail.com duduwoo 5 大家谷歌一下,都能找到电子版。别问我要电子版,我没有。我是买的纸版。特别是第 2 本书,很好, 就是那伙开发 SWT Designer 的家伙写的。 废话少说,介绍一下如何为我们的工程生成 Editor 吧。我们以后的教程要逐渐地丰富它。还是看图, 在 plugin.xml 的 extensions 页面中,单击 Add…按钮找到 org.eclipse.ui.editors,OK 后就看到了。 然后右点这个添加的 org.eclipse.ui.editors,选择 New->editor 菜单,就生成了下图的 editor. 我们要修改下面的 Extension Element Details: carolwoo@gmail.com duduwoo 6 这还不算完,我们下面先做一下准备工作:就是建立 GEF 工程的基本结构。一个最基本的 GEF 工程 应包括:模型、控制器和视图。在下面的图中可以看到我们建立了 3 个包,(另一个包是 RCP 的),其中 gef.tutorial.step.model 包中放置和模型相关的类;gef.tutorial.step.parts 包中当然放置和控制器相关的类,如 果你觉得 editpart 更清楚,也可以把这个包叫 gef.tutorial.step.editparts;gef.tutorial.step.ui 包中自然放置和视 图相关的类了,我们这里显示 GEF 图形的是在 Editor 的 Viewer 中,所以我们下面生成的 editor 类就应该 放在这个包里面。 下面我们先创建 gef.tutorial.step.ui 包中的 Eclipse 的 editor 插件,因为我们会发现,以后的大部分工作 是要在 Editor 中写代码。Eclipse 中的 Editor 是从 org.eclipse.ui.part.EditorPart 扩展而来的,因为我们要把 Editor 作为操作 GEF 的界面,所以我们这里生成的 DiagramEditor 类是从 org.eclipse.ui.part.EditorPart 的子 carolwoo@gmail.com duduwoo 7 类 org.eclipse.ui.parts.GraphicalEditor 派生而来。因为 GraphicalEditor 类可以帮助我们创建显示 GEF 图形的 视图 Viewer,这个后面会看到。 关于 GraphicalEditor 类的层次结构可以从 help.eclipse.org 中看到,我把它拷贝到了这里。 我们生成的 DiagramEditor 类放在 gef.tutorial.step.ui 包中,代码都是自动生成的,如下所示。但是我们 要加一个 ID 用于标示这个 Editor,红框内为手工加的。 然后我们要把 DiagramEditor 类和前面扩展的名为“Diagram Editor”的 Editor 扩展结合起来。还是在 plugin.xml 文件的 Extensions 页面中,在 class 那一项后面的 Browser…中找到我们的 DiagramEditor 类加上。 carolwoo@gmail.com duduwoo 8 下面我们就要想办法显示这个 DiagramEditor 了。这里我们给 RCP 工程加个菜单,这个菜单可以打开 一个空白的 GEF Editor。 加菜单的步骤如下: (1)首先要有一个 Action 的派生类。这里面定义了菜单和对应的工具按钮的显示字符串和图标,以 及其 ID 等等。这里的 Action 派生类名为 DiagramAction,被放到 gef.tutorial.step.actions 包中。 carolwoo@gmail.com duduwoo 9 因为用到了我们 RCP 工程 Application 的 ID,所以在 Application.java 文件中加入: carolwoo@gmail.com duduwoo 10 (2)然后在 ApplicationActionBarAdvisor.java 文件中添加前面创建的 DiagramAction。这里我们顺便 创建了一个 Exit 菜单和一个 About 菜单。免得我们的 Diagram 菜单看上去太单薄。 (3)在 plugin.xml 文件的 overview 页面中点击“Launce an Eclipse application”那个链接,就可以看 到加这个菜单的效果了。但是这个菜单还不能起任何作用,因为我们在 DiagramAction 那个类的 run()中没 让它做任何事情。下面我们就要让它显示一个对话框,然后打开一个 Diagram Editor。 carolwoo@gmail.com duduwoo 11 下面继续谈如何显示我们的 Diagram Edior,好麻烦呀,呵呵。 (1)因为所有的 Editor 都要有个 EditorInput 作为其内容的提供者,所以我们生成了一个 DiagramEditorInput 作为 DiagramEditor 的提供者。因为我们是 RCP 工程,所以我们的 DiagramEditorInput 是从 IPathEditorInput 派生的。这里的例子好像是从 Using GEF with EMF 中借用来的,我记不很清楚了。 我们不详细谈这个 EditorInput 的作用,有疑问的可以参考 RCP 那本书,写的很详细。另外,前面推荐的 另外商业插件开发的书也介绍的很多。 carolwoo@gmail.com duduwoo 12 (2)在就是在 DiagramAction 中的 run()函数内添加代码让 Diagram 菜单打开一个对话框。文件的扩 展名为*.diagram。 carolwoo@gmail.com duduwoo 13 (3)最后在 Perspective.java 文件中设置 Editor 为可见。 …… …… carolwoo@gmail.com duduwoo 14 (4)现在应该可以显示 Diagram Editor 了吧。运行一下试试。会出现下面的错误。 carolwoo@gmail.com duduwoo 15 唉,真是好事多磨呀。为什么呢,考虑再三,在看看错误提示,知道是 GEF 惹的祸。因为 GEF 里面 有一堆命令堆栈 Command Stack,它们都要放一个地方呀。所以我们还要修改一下 DiagramEditor.java。 Editing domain 管理命令堆栈 command stack、工具条 palette viewer 等。Editing domain 还起通知在 Graphicalviewer 中生成的 SWT 事件的作用。因此,一定要建立一个 Editing domain。这里,在 DiagramEditor的构造函数中使用setEditDomain()函数设置了一个org.eclipse.gef.DefaultEditDomain作为 Editing domain。我们要说明的是:虽然对 GEF 来讲一个 Editing domain 必须是设置成 Graphicalviewer (直白点说就是可画图的),因为我们的 DiagramEditor 是从 GraphicalEditor 派生的,所以这里的缺省的 Editing domain—DefaultEditDomain 其内在是被设置成 Graphicalviewer 的。 说明:如果我们把 Graphicalviewer 放在 Editor 中时,我们使用 DefaultEditDomain。如果把 Graphicalviewer 放在 View 中或者放在一个独立的 Eclipse 应用中时,我们要用 org.eclipse.gef.EditDomain 作为 Editing domain。 (5)这下再试试,好了吧。哈哈。不过这个 Editor 中什么也不显示。 怎么,你的怎么看不到上图。那你要先点击菜单 File->Diagram,在对话框中随便输入一个文件名, OK 就打开 Diagram Editor 了。 …… carolwoo@gmail.com duduwoo 16 下面我们就要在这个 Diagram Editor 上画图了,因为我们要讲的是 GEF,所以要按 GEF 的路子走,即 使看上去比较麻烦。什么是 GEF 的路子呢,可以看下面的图看了解一下。 Model Editpart Factory Editpart Editor中的Graphicalviewer12 3 4 基本过程: 1. 创建模型,譬如HelloModel; 2. 创建模型对应的控制器Editpart,一般命名为HelloEditPart; 1. 在Editpart中要用Draw2D函数绘制图形。 2. 还要做其他事情,后面的回里再说. 3. 创建连接模型和控制器的工厂,一般命名为**PartFactory; 4. 然后在Editor中创建Graphicalviewer,并且显示图形。 Model Editpart Factory Editpart Editor中的Graphicalviewer12 3 4 基本过程: 1. 创建模型,譬如HelloModel; 2. 创建模型对应的控制器Editpart,一般命名为HelloEditPart; 1. 在Editpart中要用Draw2D函数绘制图形。 2. 还要做其他事情,后面的回里再说. 3. 创建连接模型和控制器的工厂,一般命名为**PartFactory; 4. 然后在Editor中创建Graphicalviewer,并且显示图形。 我们就按上图的顺序和规则实现 GEF。 GEF 路子 创建模型 首先我们创建最基本的模型,因为我们现在只想在 Diagram Editor 中显示 Hello World 就好,所以我们 只有一个模型,我们把这个模型放在 gef.tutorial.step.model 包中。 carolwoo@gmail.com duduwoo 17 其实我们在这个模型文件中给出一个字符串变量 text 后,单击右键,在菜单中就可以设置 Getters 和 Setters。Anyway,这个模型够简单的了吧。 创建控制器(Controller) 下面该创建显示上面模型的视图 View 了吧,先别忙,我们要先创建连接视图和模型的控制器,在 GEF 中就是 EditPart 了。因为在创建视图的时候要执行这个控制器。靠,说是什么 MVC,我觉得够乱的。 那我们就创建一个 HelloEditorPart 吧,(其实最好写成 HelloEditPart),放在 gef.tutorial.step.parts 包中。 我们可以看到这里的 HelloEditorPart 的主要作用就是用 Draw2D 的函数作图,这里的图形就是在一个 Label 上写 Hello World 这几个字母。 carolwoo@gmail.com duduwoo 18 大家注意 import 的时候要 import 进正确的类。譬如这里就应该 import org.eclipse.draw2d.Label。因为 我们后面用的是 Graphical Viewer,我们这里的 HelloEditorPart 就从 AbstractGraphicalEditPart 派生。 这里我们使用 getModel()函数获得 HelloModel 模型。因为在 GEF 中模型被当作 Object 类型对待,所 以我们这里进行了强制转换。这个要注意,以后我们生成 Setters 时也要用 Object 类型,然后再强制转换, 后面会看到的。 这样,我们创建的 HelloModel 就可以被 EditPart 操作了,并且 EditPart 还绘制了图形。 连接模型和控制器(Controller) 现在我们已经有了模型 HelloModel,有了它的控制器 HelloEditorPart,怎么把它们联系起来呢。GEF 的方法是用工厂 Factory。为什么用工厂模式呢,现在我们只有一个模型和它的控制器,如果多了就比较麻 烦了。工厂模式大家参考模式书。 简单地说,连接模型和控制器只需两步,就是(1)首先根据模型类型创建其控制器 (2)然后用 setModel() 函数连接模型和其控制器。 carolwoo@gmail.com duduwoo 19 创建视图 View 下面就要在 DiagramEditor 中创建 Viewer 了,用来显示 HelloEditorPart 中绘制的图形的。我们这里创 建的是一个 GraphicalViewer。在 GraphicalViewer 通过其 initializeGraphicalViewer()函数接收到 HelloModel 的内容前,我们要先配置一下 GraphicalViewer。而 configureGraphicalViewer()函数中是配置 GraphicalViewer 的好地方。配置 GraphicalViewer 包括为 DiagramEditor 选择合适的 RootEditPart(决定了 editor 的工作区, 例如 GEF 包括可缩放 zoomable 和可卷动 scrollable 的工作区,以后会谈到)和 EditPartFactory(我们例子 中就是 PartFactory)。我们可以看到配置 GraphicalViewer 就是把模型和控制器在视图 GraphicalViewer 中连 接起来。 配置好 GraphicalViewer 后,我们就可以设置 GraphicalViewer 中显示的内容了,就是在 initializeGraphicalViewer()中用 setContents()函数。(我窃以为这是多此一举,不符合 MVC 的规则,因为前 面已经在视图中把模型和控制器连接起来了。) (1) 首先根据模型类型创建其控制器 (2) 然后连接模型和其控制器 carolwoo@gmail.com duduwoo 20 运行 看看我们的成功吧。我靠,GEF 竟然把 MVC 糟蹋到这种程度,看来需要改进的地方不少呀。 (1) (2) carolwoo@gmail.com duduwoo 1 第 2 回 管理多个模型 内容提要: (1)管理多个模型。其中一个模型 ContentsModel 保存整个图形的信息,这个图形中我们要用 HelloModel 的对象做三个 Hello World! (2)在整个图形的 ContentsModel 模型中使用 Layer 层的概念,给里面的图形加上颜色等效果。 上回书说到如何使用 GEF 来显示 Hello World,书接上回,我们要介绍如何把 Hello World 这个图形放 到一个图形集中,这个图形集模型保存着整个图形的信息。然后我们要使用布局管理器(layout manager) XYLayout 来绘制这个图形集,这个布局管理器可以使图形自由移动。 图形集模型 这里我们要创建一个图形集模型 CpntentsModel,这个模型包括多个 Hello World 对象。而且这个图形 集模型用于管理整个图形,譬如设置图形的布局等。 回忆一下上回书中构建一个 GEF 的过程。对了,简单地说就是: Model -----------Æ EditPart --------------Æ Editor (其实就是 Graphicalviewer) | | | EditPartFactory 对应于我们工程中的包,就是下图,在这一节中我们要反复地修改这几个文件以达到最后效果。 1 2 3 4 carolwoo@gmail.com duduwoo 2 我大胆地预计一下,估计很快会有 GEF 开发的插件了,因为我们现在总是要一个一个去创建这个咚咚, 其实可以通过一个插件一次就搞定上面几个文件。 创建图形集模型 ContentsModel 创建 ContentsModel 就不唐僧了,代码如下 创建 ContentsModel 的控制器 ContentsEditPart 下面,我们还是从 AbstractGraphicalEditPart 派生一个对应 ContentsModel 的 ContentsEditPart。我们在 继承来的 createFigure 方法中给这个图形集设置一个 Draw2D 的图层(Layer 类),这个图层的特点是透明 图层。 连接图形集模型 ContentsModel 和它的控制器 ContentsEditPart 说起来我都觉得烦,我们又要在 PartFactory.java 中连接图形集模型 ContentsModel 和它的控制器 ContentsEditPart 了。以后我们要在 PartFactory.java 中连接 n 多咚咚和它们的控制器。代码如下: carolwoo@gmail.com duduwoo 3 修改 Viewer 中显示的模型为 ContentsModel 记得我们前面在 DiagramEditor 类的 initializeGraphicalViewer 方法中把 HelloModel 设置为图形模型吧。 不记得的看第 1 回。这里我们要把 ContentsModel 设置为图形模型。 package gef.tutorial.step.ui; import gef.tutorial.step.model.ContentsModel; public class DiagramEditor extends GraphicalEditor { …… protected void initializeGraphicalViewer() { GraphicalViewer viewer = getGraphicalViewer(); //set the contents of this editor ContentsModel parent = new ContentsModel(); } } 运行 carolwoo@gmail.com duduwoo 4 如果你愿意运行一下,会发现这时 Diagram Editor 上还是没显示任何图形。下面我们就要让它显示图 形。 为图形集添加子集 如果我们不仅仅是绘制 Hello World 的话,那仅有 HelloModel 是不够的。我们还要有一个上层模型, 就是前面创建的图形集模型 ContentsModel。图形集模型 ContentsModel 和图形 HelloModel 之间是父子关 系(不是继承和派生关系,而是一个包含多个的关系)。GEF 提供了一个框架来实现这个父子关系,后面 我们会谈到。这里我们先充实这个父模型。 把 ContentsModel 变成父模型 在 GEF 中,经常会随着应用的需求,父模型会变成子模型。譬如在上一回中的 HelloModel 当时还是 父模型呢,到这里如果我们需要花 n 个 Hello World 的时候,我们就需要在其上建一个父模型 ContentsModel。 HelloModel 就要被添加到这个父模型中。自然 ContentsModel 不会只有 HelloModel 这一个孩子。 在 ContentsModel 中,我们要有: z 一个列表 child 来保存子模型; z 一个添加子模型的方法; z 一个获得子模型的方法。 把 ContentsEditPart 也变成父模型 记得前面我们说过 GEF 有个框架可以保证父子关系。为了使上面的父子关系能够顺利工作,需要在父 亲(这里是 ContentsModel)的 EditPart(这里是 ContentsEditPart)中,在 getModelChildren()方法(从 AbstractEditPart 继承而来)中返回子模型的列表 List。说白一点,就是说 ContentsModel 既然当爹了,那它 对应的 ContentsEditPart 也要承担起当爹的责任。 carolwoo@gmail.com duduwoo 5 好了,事到如今才把父子关系确定。 插曲:美化一下 Hello World 图形 在第 1 回中,我们可以看到 Hello world 是写在一块 Label 上,这里我们要给这个 label 加上橘黄色背 景,黑色边框,让它看上去美观一些。要做这些,当然是在 HelloEditPart 中完成了,为什么呢?记得第 1 回中我们说 EditPart 这个控制器还管画图吧,因为里面有个 createFigure 方法。 carolwoo@gmail.com duduwoo 6 这下在 Graphicalviewer 中显示一下父子关系吧 既然父子关系已经确定,也美化了 Hello World,那让我们在 Graphicalviewer 中显示一下吧。注意,在 这里显示的时候,我们才知道原来 HelloModel 真的是 ContentsModel 的儿子,因为 HelloModel 被加到 ContentsModel 里。这个添加过程是在 initializeGraphicalViewer 方法中完成的 package gef.tutorial.step.ui; import gef.tutorial.step.model.ContentsModel; import gef.tutorial.step.model.HelloModel; import gef.tutorial.step.parts.PartFactory; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.gef.DefaultEditDomain; import org.eclipse.gef.GraphicalViewer; import org.eclipse.gef.ui.parts.GraphicalEditor; public class DiagramEditor extends GraphicalEditor { carolwoo@gmail.com duduwoo 7 ………………………….. protected void initializeGraphicalViewer() { // 设置最上层模型 ContentsModel parent = new ContentsModel(); HelloModel child1 = new HelloModel(); // 创建一个子模型 parent.addChild(child1); // 添加 HelloModel 到 ContentsModel 中 viewer.setContents(parent); } …………………………. } 如果再运行一下,就会发现下面的效果。靠,还是什么也没有。想想我们的程序和以前有什么变化呢, 那些改变模型的地方我们不要去想,因为那不影响图形显示。和图形相关的改变是:我们现在开始显示 ContentsModel 了,它对应的 ContentsEditPart 中设置了图层。 而如果我们让 createFigure 方法返回 null: 就会: carolwoo@gmail.com duduwoo 8 在小日本的教程里能显示(见下图),要不这是 Eclipse 版本的关系,要不就是不是 RCP 工程的关系。 那我们的图形跑哪去了呢?因为我们引进了图层 Layer,这就要设置这个图层的布局才能正常显示图 形。 布局管理器 GEF 应用使用的是 Draw2D 的布局管理器,所以 GEF 的布局管理器和 Draw2D 的布局管理器几乎相 同。但是,GEF 中图形在视图中的位置和图形的大小是由其模型定义的,因此他们有点不同。关于 Draw2D 的布局管理器我们在以后的教程中介绍。 设置布局管理器 布局管理器有很多种,我们这里就用一种:org.eclipse.draw2d.XYLayout。这种布局管理器允许图形自 由地移动。 carolwoo@gmail.com duduwoo 9 此时,如果运行一下我们的程序,就会看到 HelloWorld 了。默认情况下 Hello World 的 Label 占满整 个视图。下面我们就看一下如何改变 Hello World 的尺寸和位置。 在模型类中管理约束 为了使用上面的 XYLayout,需要设置一下被该布局管理器管理的图形对象的尺寸和位置等,这就是 约束 Constraint。举个例子吧,如果你要在图形集中绘制一个矩形,那你就要有一个位置点(譬如你设置 左上点作为位置点)和一个尺寸(长和高)。再说白一点,约束就是指出图形在图形集中的位置和大小。 对图形集 ContentsModel 中的每个图形都要添加约束,因此约束被添加到他们的模型类中(记得我们 前面说过 Draw2D 和 GEF 布局管理器的不同之处吧)。这里我们只有 Hello World 一个图形类,所以我们要 在 HelloModel 中添加约束。我们这里 HelloWorld 是写在 Label 上,所以我们就给出一个矩形 Rectangle 作 为约束。 在模型中,约束是一些与实际业务无关的成员。 carolwoo@gmail.com duduwoo 10 应用约束 既然已经在模型中设置了约束,那么在什么地方把约束施加给图形呢。GEF 在 AbstractEditPart 类中提 供了 refreshVisuals()用于把约束施加给图形。因为我们是在 HelloEditorPart 中绘图的,所以我们要在 HelloEditorPart 中重载该方法(refreshVisuals 方法不是默认就被重载的)。 carolwoo@gmail.com duduwoo 11 绘制带约束的图形 最后,我们在视图中看看添加约束之后的图形是什么样的效果。这里我们绘制了三个 Hello World 图 形,他们的约束(其实就是他们的位置和大小,由矩形决定)分别是: „ Rectangle(0, 0, -1, -1) „ Rectangle(30, 30, -1, -1) „ Rectangle(10, 80, 80, 50) 大家可能奇怪,为什么第 1 和第 2 个矩形的长和宽被设置为-1 呢,这样可以使矩形的尺寸随着里面文 本的变化而变化,就是说这样的矩形正好能包含文本。而对第 3 个矩形而言,如果里面的文本太长,就会 有部分看不到了。 在 gef.tutorial.step.ui.DiagramEditor 中,添加代码如下: …… …… carolwoo@gmail.com duduwoo 12 好了,最后的效果就是: carolwoo@gmail.com duduwoo 13 carolwoo@gmail.com duduwoo 1 第 3 回 图形操作 内容提要: (1)使用 XYLayout 布局 (2)Edit 操作图形,譬如通过句柄改变图形尺寸,移动图形 (3)创建和安装 editing policy,用于改变图形尺寸,移动图形 (4)创建和执行命令 Command,用于改变图形尺寸,移动图形 (5)Undo 和 Redo 的操作 这章开始涉及到 Policy、Command、Role 等概念了。当模型改变时(譬如这里的 HelloWorld 改变尺寸 或改变位置),要产生请求 Request(org.eclipse.gef.Request),然后这请求就发送给模型对应的 EditPart(前 面讲过一个模型就对应一个 EditPart),在EditPart 中安装有 Editing Policy,当然在 EditPart 中的 Editing Policy 不只一个,所以应该是 Editing Policies。Editing Policy 的作用就是管理一些 Commond 命令,而这些命令就 是满足上面的请求的。Editing Policy 在 Eclipse 的 GEF 开发指南部分有对应表。 为了使大家明白的快点,我想举一个不恰当的例子来说明他们之间的关系。譬如说我自己就是一个模 型 Model,我老婆是对应于我的 EditPart(控制器)。我想买个数码相机(Model changing)呢,那我就产 生了相关的请求 Request,这些请求发送给我老婆 EditPart,当然我家有很多 Policies 规矩,这些规矩是我 老婆掌握(相当于把 Policies 安装到 EditPart),我老婆一看我家正好有相关的 Policy,就把这个 Policy 誊 在纸上拿给我(派生一个 Policy 类)。(注意这个 Policy 可不光管理买相机的命令 BuyCommand,可能还包 括如何处理我原来的相机 DeleteCommand 等等。)然后就说你去买吧,先执行 BuyCommand 命令。我当然 有执行命令的一系列步骤了(譬如说比价,侃价等),然后就乐颠颠买到相机了。 好,不管你明白不明白,我们首先介绍一下本节创建的类吧: z 首先,我们要有规矩,就是 Policy; z 然后把一个或多个 Policy 安装给 EditPart z 然后我们要根据请求创建命令 Command z 然后在每个 Policy 的框架下执行(一个或多个)命令 当然,执行完命令的模型满足了请求,发生了改变,就要通知控制器 EditPart,否则控制器不知道模 型改变呢,就无法通知视图来正确显示。就像上面的例子中,买了相机后就要通知老婆一下,我已经买了。 至于如何通知,我们后面再说。 模型改变 请求 org.eclipse.gef.Request EditPart 产生 发送给 Editing Policy安装到 Command 实现 管理 注册 模型改变 请求 org.eclipse.gef.Request EditPart 产生 发送给 Editing Policy安装到 Command 实现 管理 注册 carolwoo@gmail.com duduwoo 2 为了改变我们图形的尺寸和移动图形,我们都创建和修改了哪些类呢?看下图: 至于说我怎么知道改变图形尺寸要创建 XYLayoutEditPolicy 而不是其他的 Policy 呢,那说明你没好好 看 Eclipse 在线帮助的 GEF Programmer's Guide。GEF 的 EditPolicy 包含在 org.eclipse.gef.editpolicies 包中。 (1)创建 editing policy 这里我们创建一个 CustomXYLayoutEditPolicy,派生自 org.eclipse.gef.editpolicies.XYLayoutEditPolicy。 代码为: (1) 创建 Policy (2) 安装 Policy: installEditPolicy (3) 创建 Command (4) 实现 Command carolwoo@gmail.com duduwoo 3 (2)安装 editing policy 因为我们前面在 ContentsEditPart 中设置的 XYLayout,所以我们在 ContentsEditPart 的 createEditPolicies 方法中安装 CustomXYLayoutEditPolicy。 carolwoo@gmail.com duduwoo 4 注意,上面 installEditPolicy 的第一个参数是个字符串变量,它指定了安装的 editing policy 的角色 Role。 这里我们用了 EditPolicy 的一个常量来表示这个 editing policy 的角色。之所以设置这个变量是因为:一个 EditPart 可以安装很多 editing policies,如果他们的角色都相同,就是这个字符串参数相同,那么就只有最 后安装的一个 policy 是有效的。其实这个字符串变量可以为任意值,用 EditPolicy 的常量就是为了统一、 清楚。不信你可以改为 installEditPolicy(“够日的小日本”, new CustomXYLayoutEditPolicy());试试。 如果安装了 CustomXYLayoutEditPolicy,就可以选择图形的句柄(handler)了。 如果我们拖动句柄改变尺寸或者选择图形改变位置,会发现我们未遂。这是因为我们根本没有执行任 何命令去做这些事情,而是我们可以做这些事情了。 句柄 carolwoo@gmail.com duduwoo 5 在我们创建改变图形大小和尺寸的命令前,你是不是纳闷我们上面拖动句柄和拖动图形到底产生了什 么请求 Request 呢?如果你想知道的话,可以在 CustomXYLayoutEditPolicy 中重载 getCommand 方法,在 控制台打印出相应的请求。 public class CustomXYLayoutEditPolicy extends XYLayoutEditPolicy { ... public Command getCommand(Request request) { System.out.println(request.getType()); return super.getCommand(request); } ... } (3)创建 Command 其实我们改变图形尺寸和移动图形位置虽然是两个请求(分别对应于 RequestConstants#REQ_RESIZE_CHILDREN 和 RequestConstants#REQ_MOVE_CHILDREN),但是他们都 是和图形的约束相关的,所以我们在新创建一个改变约束的类 ChangeConstaintCommand 就行了。 carolwoo@gmail.com duduwoo 6 注意,上面的 execute()方法很重要,就是它执行命令的。 (4)修改 Editing Policy 创建了前面的命令,就要在 Editing Policy 的框架下运行命令了。我们的 XYLayoutEditPolicy 的 getCommand 方法得到的请求类型是 REQ_MOVE_CHILDREN 或 REQ_RESIZE_CHILDREN 时,就会执行 createChangeConstraintCommand 方法。所以我们的 ChangeConstaintCommand 要放在这个方法中执行。 完成上面的工作,运行一下吧,看看能不能改变图形尺寸和位置了。还是不能!!!!只就是我们前面 说的:模型虽然改变了,但是视图并不知道,所以要通知 EditPart 模型已经改变了,再由 EditPart 改变视 图。这就涉及到以后经常用到的监听(Listener)机制。 carolwoo@gmail.com duduwoo 7 监听模型改变 (1)在模型中设置监听器 模型有责任把自己的改变通知 EditPart,因为对所有的模型都要通知对应的 EditPart,所以创建一个抽 象类AbstractModel作为这些模型类的超类,在AbstractModel类中使用java.beans.PropertyChangeListener 接 口和 java.beans.PropertyChangeSupport 类完成这个目的。 下面,我们就使 AbstractModel 作为 HelloModel 的超类,因为是 HelloModel 改变。然后在 setConstraint 方法中调用 firePropertyChange。 carolwoo@gmail.com duduwoo 8 这里注意我们设置了一个常量 P_CONSTRAINT 作为模型中约束改变的标示。 firePropertyChange 的第 2 个参数都是 null。 (2)在 EditPart 中注册监听器 然后,我们就要在 EditPart 的 active()中注册监听器,还要在 deactive()中删除监听器。同样的道理,我 们创建一个抽象的 EditPart 类,来注册监听器。 carolwoo@gmail.com duduwoo 9 然后,我们的 HelloEditPart 要派生自这个抽象类,然后在模型改变时刷新视图。注意这里用到了我们 前面设置的变量 P_CONSTRAINT。通过这个变量我们才知道是要改变 HelloModel 模型的约束,下面的章 节中我们还要改变文本。 再运行一下吧,绝对没问题了。 EditPart 把自己注册为监听器 carolwoo@gmail.com duduwoo 10 Undo/redo 撤消/重复操作 执行过的 Command 都放在 EditDomain(还记得我们第 1 回就讲到的这个东西吧)中的 CommandStack 中。当执行 undo 操作时,该命令从命令堆栈中弹出;redo 时,则再放进去。所以我们之需要在***Command 类中重载 Command#Undo 就可以了。但是,我们还要设置一个变量以记录模型以前的状态。 那为什么我们不重载 redo()呢?从 Command 中可以看出 redo()执行的就是 execute()。 public abstract class Command { ... public void redo() { execute(); } ... } 在 Java 中,Undo/Redo 是 Action,所以我们要有菜单或者工具按钮来执行这些 Action,那我们就顺便 把如何添加工具按钮介绍一下吧。步骤如下:(不详细说明,大家去看那两本书) 记住以前的状态 这两个函数中肯定一个设置新值,一个设置旧值 carolwoo@gmail.com duduwoo 11 (1)创建一个 DiagramActionBarContributor 类。 下面内容来自八进制: http://bjzhanghao.cnblogs.com/archive/2005/03/30/128704.html 编辑器没有自己的工具条,它的菜单只能加在主菜单里。(其实这话不能这么说,SWT Designer 就是 一个反例。EditPart 在 RCP 中是可以添加工具条的。) 首先要介绍 Retarget Action 的概念,这是一种具有一定语义但没有实际功能的 Action,它唯一的作用 就是在主菜单条或主工具条上占据一个项位置,编辑器可以将具有实际 功能的 Action 映射到某个 Retarget Action,当这个编辑器被激活时,主菜单/工具条上的那个 Retarget Action 就会具有那个 Action 的功能。举 例来说,Eclipse 提供了 IWorkbenchActionConstants.COPY 这个 Retarget Action,它的文字和图标都是预先 定义好的,假设我们的编辑器需要一个"复制节点到剪贴板"功能,因为"复制节点"和"复制"这两个词的语 义十分相 近,所以可以新建一个具有实际功能的 CopyNodeAction(extends Action),然后在适当的位置调 用下面代码实现二者的映射: IActionBars.setGlobalActionHandler(IWorkbenchActionConstants.COPY,copyNodeAction) 当这个编辑器被激活时,Eclipse 会检查到这个映射,让 COPY 项变为可用状态,并且当用户按下它时去执 行 CopyNodeAction 里定义 的操作,即 run()方法里的代码。Eclipse 引入 Retarget Action 的目的是为了尽量 减少主菜单/工具条的重建消耗,并且有利于用户使用上的一致性。在 GEF 应用程序里,因为很可能存在 多个视图(例如编辑视图 和大纲视图,即使暂时只有一个视图,也要考虑到以后扩展为多个的可能),而 每个视图都应该能够完成相类似的操作,例如在树结构的大纲视图里也应该像编辑视 图一样可以删除选 中节点,所以一般的操作都应以映射到 Retarget Action 的方式建立。 (2)在 plugin.xml 文件中设置 contributorClass。 carolwoo@gmail.com duduwoo 12 运行 carolwoo@gmail.com duduwoo 1 第 4 回 删除和添加图形 内容提要 (1)删除和添加图形 (2)从视图 Graphicalviewer 修改图形后如何通知给 model 模型 (3)为什么要建立抽象模型 (4)使用 PaletterViewer。如何为 PaletterViewer 添加标准工具 Tool,添加自定义工具,设 置 PaletterViewer 的抽屉 drawer 属性视图 4.1 删除图形 下面我们首先介绍一下如何删除图形。一般来讲,删除图形需要下面的一些步骤: (1) 在工具条或者菜单上创建相应的 Action。 (2) 在图形模型 A 中创建相应的删除函数 B,并通知 Editpart 相应的改变。 (3) 当删除图形时要用模型 A 对应的 Editpart 刷新 Graphical Editor 来显示删除。 (4) 然后就要创建相应的 Command 来调用(2)中创建的删除函数 B。 (5) 在相关的 Policy 中调用前面创建的 Command。 (6) 在图形模型 A 的子模型 C 的 Editpart 中安装这个 Policy。OK! 下面我们就按这个步骤进行。 (1)打开 DiagramActionBarContributor 类,在其中添加 Delete 的 Action。 package gef.tutorial.step.actions; import org.eclipse.gef.ui.actions.ActionBarContributor; import org.eclipse.gef.ui.actions.DeleteRetargetAction; import org.eclipse.gef.ui.actions.RedoRetargetAction; import org.eclipse.gef.ui.actions.UndoRetargetAction; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.ui.actions.ActionFactory; public class DiagramActionBarContributor extends ActionBarContributor { protected void buildActions() { addRetargetAction(new UndoRetargetAction()); addRetargetAction(new RedoRetargetAction()); addRetargetAction(new DeleteRetargetAction()); } carolwoo@gmail.com duduwoo 2 protected void declareGlobalActionKeys() { // TODO Auto-generated method stub } public void contributeToToolBar(IToolBarManager toolBarManager) { toolBarManager.add(getAction(ActionFactory.UNDO.getId())); toolBarManager.add(getAction(ActionFactory.REDO.getId())); toolBarManager.add(getAction(ActionFactory.DELETE.getId())); } } (2)因此我们要删除的 HelloModel 是包含在 ContentsModel 中的,因此要在 ContentsModel 中添加删除 HelloModel 的函数。另外,我们的 AbstractModel 已经添加了 PropertyChangeSupport 来支持对 Editpart 的信息传递,因此,这里我们让 ContentsModel 继承 AbstractModel。修改 ContentsModel 的代码如下: package gef.tutorial.step.model; import java.util.ArrayList; import java.util.List; public class ContentsModel extends AbstractModel { // 定义下面的字符串用于标识该模型中结构(Children)改变 public static final String P_CHILDREN = "_children"; private List children = new ArrayList(); //The list of child models public void addChild(Object child) { children.add(child); // 添加子模型后通知 EditPart firePropertyChange(P_CHILDREN, null, null); } public List getChildren() { return children; } // 删除一个子模型 public void removeChild(Object child) { // 删除一个子模型 children.remove(child); // 删除子模型后通知 EditPart firePropertyChange(P_CHILDREN, null, null); } carolwoo@gmail.com duduwoo 3 } (3)然后,ContentsEditPart 要能感知 ContentsModel 相应的改变并修改 Graphical Editor 上图形的显示。方法和前面介绍的该改变 HelloEditPart 的方法一样。代码如下: public class ContentsEditPart extends EditPartWithListener { ... public void propertyChange(PropertyChangeEvent evt) { // 模型改变时通知 if (evt.getPropertyName().equals(ContentsModel.P_CHILDREN)) { // 因此子模型改变,要刷新子模型的 Editpart 显示其改变 refreshChildren(); } } ... } 注意这里利用 refreshChildren 方法来刷新子模型的 Editpart。 (4)要真的删除图形,还是要有相应的 Command 才行。因此我们创建了下面的 DeleteCommand: package gef.tutorial.step.commands; import gef.tutorial.step.model.*; import org.eclipse.gef.commands.Command; public class DeleteCommand extends Command { private ContentsModel contentsModel; private HelloModel helloModel; // Override public void execute() { // 删除模型 contentsModel.removeChild(helloModel); } public void setContentsModel(Object model) { contentsModel = (ContentsModel) model; } public void setHelloModel(Object model) { helloModel = (HelloModel) model; carolwoo@gmail.com duduwoo 4 } // Override public void undo() { // undo contentsModel.addChild(helloModel); } } 注意,因为 redo 默认是执行 execute 方法,因此如果没有特殊需求,不必重载它。 (5)Command 要在 Editing Policy 中调用。用户发出 Delete 命令时是发出的 REQ_DELETE 类型的 Request,因此我们要考虑在什么地方处理这个 Request。GEF 提供了 ComponentEditPolicy 来处理 REQ_DELETE 类型的 Request。在 ComponentEditPolicy 类中, 当发送 REQ_DELETE 类型的 Request 后,就调用 createDeleteCommand 方法,然后在该方 法中用前面创建的 DeleteCommand 来实现对图形的删除。 所以,下面创建一个 ComponentEditPolicy 的派生类 CustomComponentEditPolicy, 然后重载其 createDeleteCommand 方法,代码如下: package gef.tutorial.step.policies; import gef.tutorial.step.commands.DeleteCommand; import org.eclipse.gef.commands.Command; import org.eclipse.gef.editpolicies.ComponentEditPolicy; import org.eclipse.gef.requests.GroupRequest; public class CustomComponentEditPolicy extends ComponentEditPolicy { // 重载 createDeleteCommand protected Command createDeleteCommand(GroupRequest deleteRequest) { // 调用 DeleteCommand DeleteCommand deleteCommand = new DeleteCommand(); deleteCommand.setContentsModel(getHost().getParent().getModel()); deleteCommand.setHelloModel(getHost().getModel()); return deleteCommand; } } 注意:这里 getHost 方法得到的是 HelloModel 的 Editpart ,这是因为这个 CustomComponentEditPolicy 要安装到 HelloModel 对应的 HelloEditorPart 中。 (6)最后,把这个 CustomComponentEditPolicy 安装到 HelloEditPart 中。这里 使用 GEF 提供的 COMPONENT_ROLE 来标识这个安装的 Policy。 carolwoo@gmail.com duduwoo 5 public class HelloEditPart extends EditPartWithListener { ... protected void createEditPolicies() { installEditPolicy(EditPolicy.COMPONENT_ROLE, new MyComponentEditPolicy()); } ... } 最后的结果如下图所示。 4.2 添加图形 添加图形和删除图形基本过程差不多,不过我们这里要介绍 GEF 提供的 Palette,作为 一点新鲜的东西。 Palette 中放置的所有东东都称为工具 Tool。所以 Palette 就是工具箱。对于一个复杂的 绘图软件来讲,工具会有很多,因此类似的工具要放在一个工具盒中,GEF 里叫 Drawer(抽 屉)。除了自己定义的一些工具之外,GEF 还提供了一些通用的工具,譬如“选择图形”和 “选择多个图形”等。 GEF 中对工具的操作其实就是对鼠标的操作。例如,当使用“选择图形”工具时,GEF 要判断鼠标的位置,才能决定那个图形被选中,然后图形的 Editpart 作出反馈显示选中的图 形。另外,当鼠标拖动移动图形时,也要判断鼠标的运动。 当添加一个图形时,用户首先选择一个工具 Tool,然后调用安装在 Editpart(这里是父 Editpart)中的 Policy 中的 Command(这里是 CreateCommand)在鼠标点击的位置绘制一个 图形。而且,当用户拖动鼠标时,将改变图形的尺寸。 注意:这些 Tool 也可以不用放在 Palette 中。 首先,我们介绍如何创建一个带 Palette 的 Graphical Editor。 carolwoo@gmail.com duduwoo 6 4.2.1 带 Palette 的 Graphical Editor 为了实现带 Palette 的 Graphical Editor , GEF 提供了一个 org.eclipse.gef.ui.parts.GraphicalEditorWithPalette 类。 从下面的树可以看出,GraphicalEditorWithPalette 类是从 GraphicalEditor 类继承来 的,因此,我们只需要把 DiagramEditor 改为从 GraphicalEditorWithPalette 类派生,并 且要继承 getPaletteRoot 方法。 java.lang.Object org.eclipse.ui.part.WorkbenchPart org.eclipse.ui.part.EditorPart org.eclipse.gef.ui.parts.GraphicalEditor org.eclipse.gef.ui.parts.GraphicalEditorWithPalette public class HelloWorldEditor extends GraphicalEditorWithPalette { ... protected PaletteRoot getPaletteRoot() { // 后面要重载该方法,在 Palette 中加上 Tools return null; } ... } 4.2.2 创建 Palette 因为创建 Palette 实在太简单,因此我不细说,只是请大家看下面代码中的步骤。 public class HelloWorldEditor extends GraphicalEditorWithPalette { ... // 重载GraphicalEditorWithPalette protected PaletteRoot getPaletteRoot() { // (1)首先要创建一个palette的route PaletteRoot root = new PaletteRoot(); // (2)创建一个工具组用于放置常规Tools PaletteGroup toolGroup = new PaletteGroup("工具"); // (3)创建一个GEF提供的“selection”工具并将其放到toolGroup中 ToolEntry tool = new SelectionToolEntry(); toolGroup.add(tool); root.setDefaultEntry(tool); // 该(选择)工具是缺省被选择的工具 // (4)创建一个GEF提供的“Marquee多选”工具并将其放到toolGroup中 carolwoo@gmail.com duduwoo 7 tool = new MarqueeToolEntry(); toolGroup.add(tool); // (5)创建一个Drawer(抽屉)放置绘图工具,该抽屉名称为“画图” PaletteDrawer drawer = new PaletteDrawer("画图"); // 指定”创建HelloModel模型”工具所对应的图标 ImageDescriptor descriptor = AbstractUIPlugin.imageDescriptorFromPlugin(Application.PLUGIN_ID, IImageKeys.NEWHELLOMODEL); // (6)创建”创建HelloModel模型”工具 CreationToolEntry creationEntry = new CreationToolEntry( "绘制HelloModel", // The character string displayed on a palette "创建HelloModel模型", // Tool 提示 new SimpleFactory(HelloModel.class), // The factory which creates a model descriptor, // The image of 16X16 displayed on a palette descriptor);// The image of 24X24 displayed on a palette drawer.add(creationEntry); // (7)将其加到前面创建的抽屉中 // (8)最后将创建的两组工具加到root上. root.add(toolGroup); root.add(drawer); return root; } ... } 虽然我们前面使用了 CreationToolEntry 类来为“画图”抽屉 drawer 添加一个工具,但 是因为我们使用了 SimpleFactory,因此在该函数的构造函数中调用了 HelloModel 的 newInstance,还是把该 Entry 指定为绘制一个 HelloModel 图形。 运行一下,就会发现 Palette 已经加上了。 carolwoo@gmail.com duduwoo 8 4.2.3 创建模型的 Command 和删除模型一样,要新建一个模型也要有相应的 Command,下面我们就创建一个名为 CreateCommand 的 Command 来执行新建图形的任务。 package gef.tutorial.step.commands; import gef.tutorial.step.model.*; import org.eclipse.gef.commands.Command; public class CreateCommand extends Command { private ContentsModel contentsModel; private HelloModel helloModel; // override public void execute() { contentsModel.addChild(helloModel); } public void setContentsModel(Object model) { contentsModel = (ContentsModel) model; } public void setHelloModel(Object model) { helloModel = (HelloModel) model; } // override public void undo() { contentsModel.removeChild(helloModel); } } 4.2.4 调用创建模型的 Command 然后就是毫无悬念的在 XYLayoutEditPolicy 中调用创建模型的 CreateCommand,这是因 为在这个 Policy 中有 CreateRequest 请求。 public class CustomXYLayoutEditPolicy extends XYLayoutEditPolicy { ... carolwoo@gmail.com duduwoo 9 protected Command getCreateCommand(CreateRequest request) { CreateCommand command = new CreateCommand(); // 产生创建图形的尺寸和位置 Rectangle constraint = (Rectangle) getConstraintFor(request); // 获得新创建的图形 HelloModel model = (HelloModel) request.getNewObject(); // 为该图形设置前面获得的位置和尺寸 model.setConstraint(constraint); // 将新创建的图形添加到模型中, // 因为我们在第 2 页的(2)中已经把模型更改和它们的 Editpart 联系起来, // 因此,Graphical Editor 中的图形也会发生变化 command.setContentsModel(getHost().getModel()); command.setHelloModel(model); return command; } ... } 执行一下。选择“创建 HelloModel 模型”工具在 Graphical Editor 上就会出现一个带加 号的光标(这个光标是在 CreationTool 中提供的,见下面代码),在某位置单击就会创建一 个新的 HelloModel 图形,该图形的尺寸是由图形中的字符串来决定的。而如果你在创建新 的 HelloModel 图形时拖动鼠标,那么图形的尺寸由拖动决定。如下图所示。 public CreationTool() { setDefaultCursor(SharedCursors.CURSOR_TREE_ADD); setDisabledCursor(SharedCursors.NO); } carolwoo@gmail.com duduwoo 10 总结一下:要想做事,就要创建相应的 Command,并且更重要的是,还要知道把这些 Command 放到合适的 Policy 中。在 http://help.eclipse.org/help31/index.jsp 的 GEF Programmer's Guide 中的 Editing and Edit Policies 中提供了一些 Command 和 Policy 的对应表。 大家可以想想为什么删除图形的工具按钮放在工具条中,而添加图形的按钮放在 Palette 中。其实我觉得 Delete 要比 Create 简单。所以 GEF 把它们放在不同地方,也符合用户的习 惯。 carolwoo@gmail.com duduwoo 1 第 5 回 属性视图和直接编辑文本 内容提要 (1)显示属性 Properties 视图。 (2)在 Properties 中修改图形属性。 (3)直接编辑图形属性,这里是修改文本框中的文字。 5.1 属性视图 前面的例子中图形的文本都是固定的,下面我们介绍如何使用属性视图(property view) 来修改图形的属性。属性视图是通过 org.eclipse.ui.views.properties.IPropertySource 接口实 现的。 在 GEF 中,使用属性视图修改的是图形模型的属性(换句话说就是图形模型是属性视 图的数据源:当图形模型改变时(如图形尺寸或文本改变时),属性视图要反映相应改变; 而在属性视图中修改属性时,图形也要发生相应改变),因此,只需要把 IPropertySource 接口和图形模型类结合起来就行了,这就需要图形模型类实现(implements)IPropertySource 接口。 1. 因为我们要用属性视图来对应于所有的图形模型,因此我们让 AbstractModel 来实现 IPropertySource 接口,并重载这个接口中的方法。 public class AbstractModel implements IPropertySource { ****************** public Object getEditableValue() { return this; // 返回模型自身作为可编辑的属性值 } public IPropertyDescriptor[] getPropertyDescriptors() { // 如果在抽象模型中返回null会出现异常,因此这里返回一个长度为0的数组 // 后面将介绍IPropertyDescriptor数组 return new IPropertyDescriptor[0]; } public Object getPropertyValue(Object id) { // TODO Auto-generated method stub return null; } public boolean isPropertySet(Object id) { carolwoo@gmail.com duduwoo 2 // TODO Auto-generated method stub return false; } public void resetPropertyValue(Object id) { // TODO Auto-generated method stub } public void setPropertyValue(Object id, Object value) { // TODO Auto-generated method stub } } 2.下面就要修改 HelloModel,使其实际上成为属性视图的数据源。这就要重载 AbstractModel 中的相应方法。 public class HelloModel extends AbstractModel { public static final String P_CONSTRAINT = "_constraint"; // 添加字符串的 ID。这样改变图形的文本时可通知其 Editpart public static final String P_TEXT = "_text"; // ① ... public void setText(String text) { this.text = text; // 图形文本改变时通知其 EditPart firePropertyChange(P_TEXT, null, text); // ② } ... // 下面时重载 IPropertySource 接口的方法 // 其实 Property View 中用 TableView 来显示属性。第一列是属性名称,第 2 列是属性值。 // IPropertyDescriptor[]数组顾名思义就是用来设置属性名称的。这里我们只提供了一个属性, // 并命名为 Greeting public IPropertyDescriptor[] getPropertyDescriptors() { IPropertyDescriptor[] descriptors = new IPropertyDescriptor[] { new TextPropertyDescriptor(P_TEXT, "Greeting")}; return descriptors; } // 使用属性的 ID 来获得该属性在属性视图的值 public Object getPropertyValue(Object id) { carolwoo@gmail.com duduwoo 3 if (id.equals(P_TEXT)) { // 这里获得 Property view 中文本属性的值 return text; } return null; } // 判断属性视图中的属性值是否改变,如果没有指定的属性则返回 false public boolean isPropertySet(Object id) { if (id.equals(P_TEXT)) return true; else return false; } // 设置指定 Id 的属性值。如果该属性不能改变或者没有这个属性,则不作任何事 public void setPropertyValue(Object id, Object value) { if (id.equals(P_TEXT)) { // 改变文本时设置文本的属性值 setText((String) value); } } ... } 在①这个地方添加的字符串 ID 用于改变图形模型的文本时可以被 Editpart 识别。在 ②这个地方使用 firePropertyChange 方法来通知图形模型的 Editpart 图形的文本已经改变。 现在,把图形模型和属性视图已经联系起来了。 3.但是因为我们还没把属性视图和 HelloEditorPart 联系起来,因此就算你在属性 视图修改了图形的属性,在 Graphical Editor 中的图形也不会发生改变。所以,我们 要修改 HelloEditorPart 来接受文本属性改变的通知。 public class HelloEditorPart extends EditPartWithListener { ****************** public void propertyChange(PropertyChangeEvent event) { if (event.getPropertyName().equals(HelloModel.P_CONSTRAINT)) refreshVisuals(); // A view is updated. else if (event.getPropertyName().equals(HelloModel.P_TEXT)) { // 当图形模型的文本属性改变时,在Graphical Editor中的图形文本也改变 Label label = (Label) getFigure(); label.setText((String) event.getNewValue()); } } carolwoo@gmail.com duduwoo 4 ****************** } 4.这时候如果你运行程序,还是看不到 Property View,这是因为你还要在 Perspective 中加上 Property View。 package gef.tutorial.step; import org.eclipse.ui.IFolderLayout; import org.eclipse.ui.IPageLayout; import org.eclipse.ui.IPerspectiveFactory; public class Perspective implements IPerspectiveFactory { public void createInitialLayout(IPageLayout layout) { final String properties = "org.eclipse.ui.views.PropertySheet"; final String editorArea = layout.getEditorArea(); IFolderLayout leftTopFolder=layout.createFolder("LeftTop",IPageLayout.LEFT,0.34f,editorArea); leftTopFolder.addView(properties); layout.setEditorAreaVisible(true); } } 好了,运行一下程序,在 Property View 中修改文本的值,然后回车就可以看到图形的 文本也发生了改变。 carolwoo@gmail.com duduwoo 5 5.2 直接在 Graphical Editor 中编辑文本 前面的方法改变图形文本貌似很好了,但是如果能直接在 Graphical editor 中修改文 本岂不是更 kool。这不就是所谓的“所见即所得”马?下面我们就介绍两种直接编辑文本 得方法:鼠标单击选中图形后再单击鼠标;或者选中图形后按 F2。 直接文本编辑是通过 REQ_DIRECT_EDIT 类型的 Request 实现的,然后 GEF 需要相 应的 Command 来执行对图形模型的直接编辑。虽然和前面讲的方法一样,直接编辑文本也 是要在一个编辑 Policy 类中执行 Command,但是所不同的是,在创建编辑 Policy 和 Command 之前,首先需要创建 org.eclipse.gef.tools.DirectEditManager 来执行对图形文本的直接编辑。 DirectEditManager 所起的作用是指定 Cell Editor 和设置 cell editor 的位置。 5.2.1 创建 DirectEditManager 首先,DirectEditManager 是一个抽象类,因此我们要派生自己的实际子类。下面我们 线介绍一下 DirectEditManager 的构造函数的参数。 public DirectEditManager(GraphicalEditPart source, Class editorType, CellEditorLocator locator) 这里,source 对应与需编辑模型的 Editpart。editorType 指定了用于编辑模型属性的 cell editor 的类型。这里的 cell editor 是从 org.eclipse.jface.viewers.CellEditor 派生的。而管理 cell editor 所处为止的类由 locator 指定,这个 locator 要实现 org.eclipse.gef.tools.CellEditorLocator 接口。 1.下面,在 package gef.tutorial.step.parts;包中创建一个 DirectEditManager 的派生类 CustomDirectEditManager。这里把 cell editor 设置为 Text 控件。 package gef.tutorial.step.parts; import gef.tutorial.step.model.HelloModel; import org.eclipse.gef.GraphicalEditPart; import org.eclipse.gef.tools.CellEditorLocator; import org.eclipse.gef.tools.DirectEditManager; import org.eclipse.swt.widgets.Text; public class CustomDirectEditManager extends DirectEditManager { private HelloModel helloModel; // 要修改该模型的文本属性 public CustomDirectEditManager(GraphicalEditPart source, Class editorType, CellEditorLocator locator) { super(source, editorType, locator); carolwoo@gmail.com duduwoo 6 // 获得 HelloModel 模型 helloModel = (HelloModel) source.getModel(); } @Override protected void initCellEditor() { // 在显示一个 cell editor 之前,先给它设置一个值 // 这里的值是获得图形模型的文本 getCellEditor().setValue(helloModel.getText()); // 在所选中的 TextCellEditor 的 Text 控件中的所有文本都显示为选择状态 Text text = (Text) getCellEditor().getControl(); text.selectAll(); } } 在前面创建的派生类 CustomDirectEditManager 的构造函数中获得 HelloModel 是因为,要在 initCellEditor 函数中把 HelloModel 的文本值设置为 cell editor 的初始值。 2.然后,要考虑把 CustomDirectEditManager 中创建的 Text 控件放在什么地方 的问题了。我们从 org.eclipse.gef.tools.CellEditorLocator 中派生一个 CustomCellEditorLocator 类来负责这个事情。 package gef.tutorial.step.parts; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.gef.tools.CellEditorLocator; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Text; public class CustomCellEditorLocator implements CellEditorLocator { private IFigure figure; // (1)Text 控件要处于 Figure 所在的为止 public CustomCellEditorLocator(IFigure f) { figure = f; // (2)因此要在构造函数中得到为哪个 Figure 设置 Text 控件 } public void relocate(CellEditor celleditor) { carolwoo@gmail.com duduwoo 7 Text text = (Text) celleditor.getControl(); // Text 控件尺寸和 Figure 一样 Rectangle rect = figure.getBounds().getCopy(); figure.translateToAbsolute(rect); text.setBounds(rect.x, rect.y, rect.width, rect.height); } } 3.为了满足直接编辑图形模型文本属性的需求,要在 HelloEditorPart 中重载 EditPart#performRequest 方法。该方法用于处理一些特定的 Request,例如“直接编辑模型 属性”等。 注意:而其他一些标准的 Request,例如图形尺寸改变、图形移动、新图形的创建则由 安装在 Editpart 中的 editing policy 直接处理。 下面,在 HelloEditorPart 中添加接受 REQ_DIRECT_EDIT 类型的 Request 的方法。 public class HelloEditPart extends EditPartWithListener { ... private MyDirectEditManager directManager = null; ... // 重载 public void performRequest(Request req) { // 如果 Request 是 REQ_DIRECT_EDIT,则执行直接编辑属性的辅助函数 performDirectEdit if (req.getType().equals(RequestConstants.REQ_DIRECT_EDIT)) { performDirectEdit(); return; } super.performRequest(req); } private void performDirectEdit() { if (directManager == null) { // 如果还没有 directManager,则创建一个:类型是 Text,位置由图形决定 directManager = new MyDirectEditManager(this, TextCellEditor.class, new MyCellEditorLocator(getFigure())); } directManager.show(); // 显示这个 directManager } } 现在运行一下程序,如下图所示。这时文本框能出现了,其实就是在原来的图形上面又 盖了一个 Text 控件而已。但是当你改变文本框的文本并回车后,会发现实际上文本框还是 显示的原来的字符串,这是因为我们还要有相关的 Command 类和安装相应的 Policy。 carolwoo@gmail.com duduwoo 8 提示:因为我们前面在 CellEditorLocator 中设置的 Text 控件和图形尺寸一样,所以当 你输入一个特别长的字符串时文本框尺寸不变。如果重载 CellEditorLocator#relocate 的话, 可以在编辑文本时改变 Text 控件的长度等。 5.2.2 创建 Command 和 DirectEditPolicy 因为在 Cell editor 中改变 HelloModel 的文本实际上要在 HelloWorld 的模型中反应出来, 因此要重建一个 Command 和一个 Policy。我们要从 DirectEditPolicy 中派生一个类来用于 Editing policy。 1.因为 Policy 中都是用于执行 Command 的,所以我们先创建一个 Command 类 DirectEditCommand。当然要放在 package gef.tutorial.step.commands;包中。 package gef.tutorial.step.commands; import gef.tutorial.step.model.HelloModel; import org.eclipse.gef.commands.Command; public class DirectEditCommand extends Command { private String oldText, newText; private HelloModel helloModel; // override public void execute() { // oldText 用于记录以前的文本 oldText = helloModel.getText(); // 设置为新的文本 helloModel.setText(newText); } public void setModel(Object model) { helloModel = (HelloModel) model; } carolwoo@gmail.com duduwoo 9 public void setText(String text) { newText = text; } // override public void undo() { helloModel.setText(oldText); } } 如前所述,因为 redo 默认是执行 execute 的,因此,如果二者要干的活一样的话,就没 必要重载 redo 了。 2.下面从 DirectEditPolicy 派生一个 CustomDirectEditPolicy 类放在 package gef.tutorial.step.policies;包中。 package gef.tutorial.step.policies; import gef.tutorial.step.commands.DirectEditCommand; import org.eclipse.gef.commands.Command; import org.eclipse.gef.editpolicies.DirectEditPolicy; import org.eclipse.gef.requests.DirectEditRequest; public class CustomDirectEditPolicy extends DirectEditPolicy { @Override // 当选中 cell editor,修改文本,cell editor 失去焦点之前执行 getDirectEditCommand 方法 protected Command getDirectEditCommand(DirectEditRequest request) { DirectEditCommand command = new DirectEditCommand(); command.setModel(getHost().getModel()); // 从 cell editor 中得到 newText 来给 Figure 设置文本 command.setText((String) request.getCellEditor().getValue()); return command; } @Override // showCurrentEditValue 方法用于显示 Figure 中的当前直接编辑值。 // 虽然 CellEditor 可能盖住了图形对该值的显示,但是更新图形会使其最优尺寸适应新值 protected void showCurrentEditValue(DirectEditRequest request) { // TODO Auto-generated method stub } carolwoo@gmail.com duduwoo 10 } 3.下面就要把这个 Policy 安装到 Editpart 中。 public class HelloEditorPart extends EditPartWithListener { ... protected void createEditPolicies() { installEditPolicy(EditPolicy.COMPONENT_ROLE, new MyComponentEditPolicy()); installEditPolicy(EditPolicy.DIRECT_EDIT_ROLE, new MyDirectEditPolicy()); } ... } 现在运行一下程序,当第 2 次单击鼠标时,就可以编辑文本了。下面我们介绍一下如 何用键盘实现相同的操作。 5.2.3 键盘操作直接编辑文本 涉及到键盘操作就要用 Action 处理,这个 Action 可以利用 GEF 提供的 DirectEditAction。创建和注册该 Action 可以在 GraphicalEditor#createActions 中完成。 在 DiagramEditor 中,我们要重载 createActions 方法。 public class DiagramEditor extends GraphicalEditorWithPalette { ... // 重载 protected void createActions() { super.createActions(); ActionRegistry registry = getActionRegistry(); // 创建并注册一个 DirectEditAction IAction action = new DirectEditAction((IWorkbenchPart) this); registry.registerAction(action); // 当一个 action 需要由选择对象更新时,需要注册其 ID getSelectionActions().add(action.getId()); } ... } 因为 DirectEditAction 被注册到 action registry 中,该 action 通过 keystroke 和 editor 联系起来。Keystroke 是由 org.eclipse.gef.KeyHandler 管理的。下面我们添加 Del 键删除图形和 F2 键编辑图形,这部分代码在 DiagramEditor 的 configureGraphicalViewer 中实现。 carolwoo@gmail.com duduwoo 11 public class DiagramEditor extends GraphicalEditorWithPalette { ... protected void configureGraphicalViewer() { super.configureGraphicalViewer(); GraphicalViewer viewer = getGraphicalViewer(); // EditPartFactory の作成と設定 viewer.setEditPartFactory(new MyEditPartFactory()); // 创建键盘句柄 keyhander KeyHandler keyHandler = new KeyHandler(); // 按 DEL 键时执行删除 Action keyHandler.put( KeyStroke.getPressed(SWT.DEL, 127, 0), getActionRegistry().getAction(GEFActionConstants.DELETE)); // 按 F2 键时执行直接编辑 Action keyHandler.put( KeyStroke.getPressed(SWT.F2, 0), getActionRegistry().getAction(GEFActionConstants.DIRECT_EDIT)); // 为当前 GraphicalView 设置 keyhandler getGraphicalViewer().setKeyHandler(keyHandler); } ... } 现在运行程序就可以用 Del 键删除图形,用 F2 键修改文本了。如果你用下面的代码取 代 getGraphicalViewer().setKeyHandler(keyHandler);,则可以用方向键来选择不同的图形。可 以自己试一下。 getGraphicalViewer().setKeyHandler(new GraphicalViewerKeyHandler( getGraphicalViewer()).setParent(keyHandler)); 建议:去看看 RequestConstants 常数中都提供了那些 Request。嘿嘿,这里的 REQ_SELECTION_HOVER 我觉得挺有用的。 回忆一下: 1.属性视图 (1)因为我们要在属性视图中反映图形模型的属性,因此抽象图形模型要实现 IPropertySource 接口。 (2)然后在具体的图形模型类中重载 IPropertySource 接口的一些函数设置视图模型 中要显示的模型属性。 (3)因为要在图形中反映属性视图中的改变,因此我们要修改图形模型的 Editpart carolwoo@gmail.com duduwoo 12 类。 2.直接编辑 (1)首先我们要有一个DirectEditManager和CellEditorLocator来设置cell editor 的类型(好像只能是 Text 和 Combo?)和位置。 (2)然后在相关的 Editpart 中重载 performRequest,如果是 REQ_DIRECT_EDIT 类型的 Request 的话,则设置 DirectEditManager。 (3)重建相应的 Policy 和 Command 来执行直接编辑。 (4)回到相关的 Editpart 中安装 Policy。 问题: 1. 如何在 Property View 中显示多个属性值。 2. 如何在 Property View 中排序属性。 3. 如何设置 Property View 中的某项属性不可编辑。 4. 如何设置 Property View 中的某项属性只能为数值。 等等。 carolwoo@gmail.com duduwoo 1 第 6 回 连接 Connection 前面我们介绍了如何创建模型和编辑操作模型,从本节开始我们要用三节来介绍对连接的操作了。其 实说白了,这些操作也就是创建连接、删除连接和移动连接等。首先我们要说说什莫是 GEF 中的连接。 GEF 的连接 GEF 中的连接也被当成模型来看待,所以连接也要有自己的控制器 EditPart 和图形 IFigure(譬如要绘 制那种自定义箭头的连接时,我们在遥遥无期的 Draw2D 教程中会讲到)。但是,不像其他模型那样,连 接没有前面提到的模型的父子关系,而是有起点 Source 和终点 Target 的关系,连接的起点和终点都被称为 节点 node。还是看图吧,图中的连接是没有箭头的连接,当然你可以自己在连接的 IFigure 中自己定义任 意形状的箭头。 既然连接没有父子关系,那前面提到的 getModelChildren、setContents 等方法就没法用了。不过 GEF 的 AbstractGraphicalEditPart 类提供了 getModelSourceConnections 方法操作起点、提供了 getModelTargetConnections 方法操作终点。另外还有一堆类和方法操作连接,这里说多了,你看着也累, 一会儿就忘,不如下面边写代码边理解。 首先创建连接模型 ConnectionModel 及其控制器 EditPart 连接既然也被作为模型看待,我们就要先创建连接的模型。注意,连接的起点和终点信息就在连接模 型中管理。 1. 和前面一样的道理,我们先创建一个抽象的连接模型类 AbstractConnectionModel 来管理连接的通用 信息。这时,AbstractConnectionModel 类还是空的。 package gef.tutorial.step.model;    public abstract class AbstractConnectionModel {  }  2. 下面我们就创建一个具体的类 LineConnectionModel。当然这个类是从 AbstractConnectionModel 类 派生的。 package gef.tutorial.step.model;    public class LineConnectionModel extends AbstractConnectionModel {  Source Target 起点 (又名 connecting agency) 终点 (又名 connecting place) 连接 carolwoo@gmail.com duduwoo 2 }  3. 再就要创建对应于上面模型的控制器了。和 HelloModel 不同的是,连接的模型应该是从 org.eclipse.gef.editparts.AbstractConnectionEditPart 派生来的。这样派生出来的控制器 EditPart 缺省会绘制多 义线连接 PolylineConnection。 package gef.tutorial.step.parts;    import org.eclipse.gef.editparts.AbstractConnectionEditPart;    public class LineConnectionEditPart extends AbstractConnectionEditPart {      @Override    protected void createEditPolicies() {     // TODO Auto‐generated method stub    }  }  4. 下面就要把模型和它的控制器联系起来。这事前面都作了 100 遍了。   package gef.tutorial.step.parts;    import gef.tutorial.step.model.ContentsModel;  import gef.tutorial.step.model.HelloModel;  import gef.tutorial.step.model.LineConnectionModel;  import org.eclipse.gef.EditPart;  import org.eclipse.gef.EditPartFactory;    public class PartFactory implements EditPartFactory {      *******************************************    private EditPart getPartForElement(Object modelElement) {     if (modelElement instanceof ContentsModel)       return new ContentsEditPart();     else if (modelElement instanceof HelloModel)       return new HelloEditorPart();     else if(modelElement instanceof LineConnectionModel)       return new LineConnectionEditPart();       throw new RuntimeException( ʺCanʹt create part for model element: ʺ      + ((modelElement != null) ? modelElement.getClass().getName() : ʺnullʺ));    }  }  carolwoo@gmail.com duduwoo 3 接着修改节点模型 1.创建了连接模型后,我们要考虑这个连接到底是连接那个图形模型的,就是说哪个图形是这个连 接的节点。很明显,HelloModel 就是作为节点的那个图形模型。注意,在这个例子中 HelloModel 既是连 接的起点 source,也是连接的终点 target。所以,我们要修改 HelloModel 模型,使其管理连接的起点和 终点的信息。    package gef.tutorial.step.model;    import java.util.ArrayList;  import java.util.List;    import org.eclipse.draw2d.geometry.Rectangle;  import org.eclipse.ui.views.properties.IPropertyDescriptor;  import org.eclipse.ui.views.properties.TextPropertyDescriptor;    public class HelloModel extends AbstractModel {  ***************     //connection ////////////////////////////////////     public static final String P_SOURCE_CONNECTION=ʺ_source_connectionʺ;     public static final String P_TARGET_CONNECTION=ʺ_target_connectionʺ;           private List sourceConnection=new ArrayList();     private List targetConnection=new ArrayList();     //////////////////////////////////////////////////  ***************    //connection ////////////////////////////////////      public void addSourceConnection(Object connx){       sourceConnection.add(connx);       firePropertyChange(P_SOURCE_CONNECTION,null,null);      }        public void addTargetConnection(Object connx){       targetConnection.add(connx);       firePropertyChange(P_TARGET_CONNECTION,null,null);      }        public List getModelSourceConnections(){       return sourceConnection;      }        public List getModelTargetConnections(){       return targetConnection;      }    carolwoo@gmail.com duduwoo 4     public void removeSourceConnection(Object connx){       sourceConnection.remove(connx);       firePropertyChange(P_SOURCE_CONNECTION,null,null);      }        public void removeTargetConnection(Object connx){       targetConnection.remove(connx);       firePropertyChange(P_TARGET_CONNECTION,null,null);      }  }      2.HelloModel 都包含连接的信息了,但是要想真把 HelloModel 当成节点来看待,我们还要做一些 工作。不用说你就明白了,不就是修改 HelloEditorPart 吗?其实就这末点东西,不是动动这个,就是动动 那个而已。如果真要把 HelloModel 当成节点来看待,那 HelloModel 的控制器 HelloEditorPart 要实现 org.eclipse.gef.NodeEditPart 接口的一些方法。哎,谁让 HelloModel 要承担这个责任呢。如果你一在 HelloEditorPart 后面加上 implements NodeEditPart,就会自动地继承下面的 4 个方法。    public class HelloEditorPart extends EditPartWithListener implements NodeEditPart{    // Abstract methods from NodeEditPart    public ConnectionAnchor getSourceConnectionAnchor(ConnectionEditPart connection) {    return new ChopboxAnchor(getFigure());    }      public ConnectionAnchor getTargetConnectionAnchor(ConnectionEditPart connection) {    return new ChopboxAnchor(getFigure());    }      public ConnectionAnchor getSourceConnectionAnchor(Request request) {    return new ChopboxAnchor(getFigure());    }      public ConnectionAnchor getTargetConnectionAnchor(Request request) {    return new ChopboxAnchor(getFigure());    }  }      这里可以看出,其实 HelloEditorPart 就是用于管理连接的锚点 Anchor 的。缺省情况下,使用的是 org.eclipse.draw2d.ChopboxAnchor 锚点。这样,我们就看到了一些情况:连接 LineConnectionModel 不是直 接和节点相连的,而是通过锚点和节点联系起来的。 3.还记得前面吗?你移动图形但是图形不动,我们就是修改 propertyChange 把问题解决的。这里也 要不段刷新连接的起点和终点,下面的 refreshSourceConnections 和 refreshTargetConnections 不知道自己要 干什么呀,但是它们暗地里调用的 getModelSourceConnections 和 getModelTargetConnections,所以,我们 继承这两个函数,让它们分别得到连接的起点和终点就行了。 public class HelloEditorPart extends EditPartWithListener implements NodeEditPart{  carolwoo@gmail.com duduwoo 5     ************************    public void propertyChange(PropertyChangeEvent event) {        if (event.getPropertyName().equals(HelloModel.P_CONSTRAINT))          refreshVisuals(); // A view is updated.        else if (event.getPropertyName().equals(HelloModel.P_TEXT)) {            // Since the text of the model was changed, the text displayed on a view is updated            Label label = (Label) getFigure();            label.setText((String) event.getNewValue());        }        else if (event.getPropertyName().equals(HelloModel.P_SOURCE_CONNECTION))           refreshSourceConnections();        else if (event.getPropertyName().equals(HelloModel.P_TARGET_CONNECTION))           refreshTargetConnections();    }       protected List getModelSourceConnections(){     return ((HelloModel)getModel()).getModelSourceConnections();    }      public List getModelTargetConnections(){     return ((HelloModel)getModel()).getModelTargetConnections();    }  }    该松口气了,但是这些才只是准备工作,真正的绘制工作还没开始呢。譬如说,连接的模型需要丰富、 在 Palette 上要加上连接的工具、创建连接的命令 Command、创建调用这个 Command 的 Policy、安装这个 Policy 等等。下面我们一一讲来。不过我还是要唐僧一下,把上面作的步骤用一个图表示。 carolwoo@gmail.com duduwoo 6 先在 Palette 上加上“连接”工具 柿子都捡软的捏,介绍例子也要从简单的入手。换句话说,就是兵马未动,粮草先行。我们先复习一下如 何在 Palette 上加上连接的工具吧。分一下几步: 1. 再 icon 那个目录中添加一个图标文件,这里的文件名是 arrowConnection.gif。 2. 然后在辅助接口中添加这个图标。 package gef.tutorial.step.helper;    public interface IImageKeys {    public static final String EDITORTITLE = ʺicons/example.gifʺ;    public static final String NEWHELLOMODEL = ʺicons/newModel.gifʺ;    public static final String NEWCONNECTION = ʺicons/newConnection.gifʺ;    public static final String ARROWCONNECTION = ʺicons/arrowConnection.gifʺ;  }  3. 最后在 DiagramEditor.java 这个文件中添加“连接”工具。需要注意的代码部分用红色显示。   public class DiagramEditor extends GraphicalEditorWithPalette  {    ************************************    protected PaletteRoot getPaletteRoot() {        // The route of a palette        PaletteRoot root = new PaletteRoot();    (1) (2) (3) (4) (5) (6) carolwoo@gmail.com duduwoo 7       // The group which stores tools other than model creation        PaletteGroup toolGroup = new PaletteGroup(ʺ工具ʺ);          // Creation and an addition of a ʹselectionʹ tool        ToolEntry tool = new SelectionToolEntry();        toolGroup.add(tool);        root.setDefaultEntry(tool); // The tool which becomes active by the default          // Creation and an addition of a ʹenclosure frameʹ tool        tool = new MarqueeToolEntry();        toolGroup.add(tool);          ///////////////////////////////////////////////////////////////        //add the draw tool drawer        // The group which stores the tool which creates a model        PaletteDrawer drawer = new PaletteDrawer(ʺ画图ʺ);          ImageDescriptor  descriptor  =  AbstractUIPlugin.imageDescriptorFromPlugin(Application.PLUGIN_ID,  IImageKeys.NEWHELLOMODEL);          // Creation and an addition of a ʹcreation of modelʹ tool        CreationToolEntry creationEntry =          new CreationToolEntry(            ʺ绘制 HelloModelʺ, // The character string displayed on a palette            ʺ创建 HelloModel 模型ʺ, // Tool  提示            new SimpleFactory(HelloModel.class), // The factory which creates a model            descriptor, // The image of 16X16 displayed on a palette            descriptor);// The image of 24X24 displayed on a palette        drawer.add(creationEntry);          //add the connection tool drawer        // The group which stores the tool which creates a connection        PaletteDrawer connectionDrawer = new PaletteDrawer(ʺ连接ʺ);          ImageDescriptor  newConnectionDescriptor  =  AbstractUIPlugin.imageDescriptorFromPlugin(Application.PLUGIN_ID, IImageKeys.NEWCONNECTION);          //  在这个抽屉里放入 New Connection 工具        ConnectionCreationToolEntry connxCreationEntry =          new ConnectionCreationToolEntry(            ʺ简单连接ʺ, //  这是显示在 palette 上的文字            ʺ创建最简单的连接ʺ, //  鼠标指在“连接”工具上时的提示            new SimpleFactory(LineConnectionModel.class), // The factory which creates a model            newConnectionDescriptor, //  在 palette 上的 16X16  图标            newConnectionDescriptor);//  在 palette 上的 24X24  图标        connectionDrawer.add(connxCreationEntry);  carolwoo@gmail.com duduwoo 8         root.add(toolGroup);        root.add(drawer);        root.add(connectionDrawer);          return root;    }    *******************************************************************************************  }      修改连接模型 在前面我们建立的连接抽象模型 AbstractConnectionModel 及其具体模型 LineConnectionModel 中,没 有任何代码,那这些模型肯定不干活了。我们要想让人家干活,至少在这些模型中添加连接的起点(Source) 和终点(Target)信息,作为 POJO,肯定有这些起点和终点信息的 setter 和 getter 方法。另外,为了方便连接 模型对连接的操作(添加和删除连接),我们也实现了一些相关方法 attachSource、attachTarge 和 detachSource、detachTargett,其实这些方法只是调用了起点和终点模型(这里是 HelloModel)中的一些方 法而已。 因为上述操作对所有的连接都有效,所以我们将他们添加到 AbstractConnectionModel 中。 package gef.tutorial.step.model;  出现这一块就对了 carolwoo@gmail.com duduwoo 9   public abstract class AbstractConnectionModel {      private HelloModel source, target;       //this connectionʹs root is connected to source  链接的头端添加到 source    public void attachSource(){    if(!source.getModelSourceConnections().contains(this))     source.addSourceConnection(this);    }      //this connectionʹs tip is connected to source  链接的尾端添加到 target    public void attachTarget(){    if(!target.getModelTargetConnections().contains(this))     target.addTargetConnection(this);    }      //this connectionʹs root is removed from source    public void detachSource(){    source.removeSourceConnection(this);    }      //this connectionʹs tip is removed from target    public void detachTarget(){    target.removeTargetConnection(this);    }      public HelloModel getSource() {    return source;    }      public void setSource(HelloModel model) {    source = model;    }      public HelloModel getTarget() {    return target;    }      public void setTarget(HelloModel model) {    target = model;    }  }  carolwoo@gmail.com duduwoo 10 实现连接 为清楚起见,我们可以按上面的步骤还完成对连接 Connection 的实现。其中,第 1 步我们已经完成了。 下面从第 2 步开始。 (2)创建连接命令。 首先我们要创建连接的命令,和创建模型的命令不同之处在于:因为连接涉及到起点和终点,所以创 建连接的命令要分两步。当用户要创建一个连接时,首先要选中一个模型 HelloModel1 作为起点,但是在 此时并知道要连接的终点是谁?所以,这时候只是记住谁是连接的起点而已,并没有创建真的连接。如果 用户又选中了另外一个模型 HelloModel2 作为终点,并且这个模型不是作为起点的模型 HelloModel1≠HelloModel2,那末这个连接就创建了。下面是相关的代码: public class CreateConnectionCommand extends Command {    private HelloModel source, target; //两个模型一个用于起点,一个用于终点    private AbstractConnectionModel connection; //连接的模型    //首先判断是否能执行连接    public boolean canExecute(){  (1)丰富连接模型 (2)创建连接命令 (3)连接相关的 Policy (4) 安装连接相关的 Policy carolwoo@gmail.com duduwoo 11   //execution is impossible when source or target is null    if(source==null||target==null)     return false;    // if source is equal to target, the execution is not possible    if(source.equals(target))     return false;    return true;    }       public void execute(){    //执行的时候分两步:连接起点和连接终点    connection.attachSource();     connection.attachTarget();    }      public void setConnection(Object model) {    connection = (AbstractConnectionModel) model;    }      public void setSource(Object model) {    source = (HelloModel)model;    connection.setSource(source);    }      public void setTarget(Object model) {    target = (HelloModel)model;    connection.setTarget(target);    }       //同样,撤销 Undo 的时候也要分两步:撤销起点和撤销终点    public void undo(){    connection.detachSource();    connection.detachTarget();    }  }  (3)实现连接相关的 Policy。 没有规矩就没有方圆。命令 Command 制定的再好,也要在专门的条条框框 Policy 下去执行。这些条 条框框就是针对某一系列操作而制定的。这样做的好处是:命令制定一次,就可以在各个条条框框 Policy 下执行;另外,一个条条框框管一系列的事,这样追究起责任来也容易。譬如说这里吧,负责创建连接的 Policy 是 org.eclipse.gef.editpolicies.GraphicalNodeEditPolicy。所以我们的 Policy 也要从它派生。一旦派 生出来,就继承下面的四种方法,但是对于创建连接来说,只关心其中的两个 getConnectionCompleteCommand 和 getConnectionCreateCommand 就好了。从这两个方法的名字就 可以看出来了,getConnectionCreateCommand 用于创建连接的命令,而 getConnectionCompleteCommand 用于完成连接。一般(A)在 getConnectionCreateCommand 中生 成一个创建连接的命令 CreateConnectionCommand,(B)然后把命令用 setStartCommand 保存在命令 carolwoo@gmail.com duduwoo 12 堆栈中,( C ) 而在 getConnectionCompleteCommand 中用 getStartCommand 方法再把 CreateConnectionCommand 命令取出来,一旦寻找到了终点,这个连接就创建了。  public class CustomGraphicalNodeEditPolicy extends GraphicalNodeEditPolicy {      @Override    protected Command getConnectionCompleteCommand(CreateConnectionRequest request) {    //命令是从 request 中获得(C)    CreateConnectionCommand command=(CreateConnectionCommand)request.getStartCommand();     // setup the target    command.setTarget(getHost().getModel());    return command;    }      @Override    protected Command getConnectionCreateCommand(CreateConnectionRequest request) {    CreateConnectionCommand command=new CreateConnectionCommand();(A)    //setup the connection model    command.setConnection(request.getNewObject());    //setup the source    command.setSource(getHost().getModel());    //创建连接的命令被记录  (B)    request.setStartCommand(command);     return command;    }      @Override    protected Command getReconnectTargetCommand(ReconnectRequest request) {    // TODO Auto‐generated method stub    return null;    }      @Override    protected Command getReconnectSourceCommand(ReconnectRequest request) {    // TODO Auto‐generated method stub    return null;    }  }    (4)安装实现连接相关的 Policy。 下面就超级简单了,只需要在 HelloEditorPart 中把连接 Policy 安装上就好了。须注意的是,虽然我们 说的是连接,但是上面创建的 Policy 并不是安装在 LineConnectionEditPart 中。 public class HelloEditorPart extends EditPartWithListener implements NodeEditPart{    *************************************    protected void createEditPolicies() {  carolwoo@gmail.com duduwoo 13       installEditPolicy(EditPolicy.COMPONENT_ROLE,new CustomComponentEditPolicy());        installEditPolicy(EditPolicy.DIRECT_EDIT_ROLE,new CustomDirectEditPolicy());        installEditPolicy(EditPolicy.GRAPHICAL_NODE_ROLE,new CustomGraphicalNodeEditPolicy());    }    ****************************************  }  运行一下吧,应该没问题了。 但是这时候如果你要删除一个模型,会发现和被删除模型相干的连接并没有被删除?韦什莫呢?要知 道天下没有免费的晚餐,你又要马儿跑,又要马儿不吃草,可能吗?下面我们就介绍如何删除连接。 carolwoo@gmail.com duduwoo 14 删除模型的时候也删除与其相关的连接 我们前面已经介绍了如何删除模型,其实就是在 DeleteCommand.java 中实现的。一个模型可能作为起 点 Source 或终点 Target。所以删除模型的时候就要检查所有把该模型作为起点或终点的连接,把他们统统 删除。 public class DeleteCommand extends Command {     private ContentsModel contentsModel;     private HelloModel helloModel;           //‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐Connection List‐‐‐‐‐‐‐‐‐‐‐     private List sourceConnections=new ArrayList();     private List targetConnections=new ArrayList();     ////////////////////////////////////////////       // Override     public void execute() {      //‐‐‐‐‐‐‐‐‐ delete the connection ‐‐‐‐‐‐‐‐‐‐‐‐‐      //在删除一个模型对象前,搜索把这个模型对象作为起点和终点的所有连接      sourceConnections.addAll(helloModel.getModelSourceConnections());      targetConnections.addAll(helloModel.getModelTargetConnections());      //删除该模型对象对应的 source      for(int i=0;i
还剩124页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

benjac

贡献于2012-06-16

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf