[Eclipse]GEF入门系列(三、应用实例)

构造一个GEF应用程序通常分为这么几个步骤:设计模型、设计EditPart和Figure、设计 EditPolicy和Command,其中 EditPart是最主要的一部分,因为在实现它的时候不可避免的 要使用到EditPolicy,而后者又涉及到Command。

现在我们来看个例子,它的功能非常简单,用户可以在画布上增加节点(Node)和节点间 的连接,可以直接编辑节点的名称以及改变节点的位置,用户可以撤消/重做任何操作,有一 个树状的大纲视图和一个属性页。这是一个Eclipse的项目打包文件,在Eclipse里导入后运 行Run-time Workbench,新建一个扩展名为”gefpractice”的文件就会打开这个编辑器。

图1 Practice EdiTor的使用界面

你可以参考着代码来看接下来的内容了,让我们从模型开始说起。模型是根据应用需求来 设计的,所以我们的模型包括代表整个图的Diagram、代表节点的Node和代表连接的 Connection这些对象。我们知道,模型是要负责把自己的改变通知给EditPart的,为了把这 个功能分离出来,我们使用名为Element的抽象类专门来实现通知机制,然后让其他模型类继 承它。Element类里包括一个PropertyChangeSupport类型的成员变量,并提供了 addPropertyChangeListener()、removePropertyChangeListener()和 fireXXX()方法分别用 来注册监听器和通知监听器模型改变事件。在GEF里,模型的监听器就是EditPart,在 EditPart的active ()方法里我们会把它作为监听器注册到模型中。所以,总共有四个类组成 了我们的模型部分。

在前面的贴子里说过,大部分GEF应用程序都是实现为EdiTor的,这个例子也不例外,对 应的EdiTor名为PracticeEdiTor。这个EdiTor继承了GraphicalEdiTorWithPalette类,表示 它是一个具有调色板的图形编辑器。最重要的两个方法是 configureGraphicalViewer()和 initializeGraphicalViewer(),分别用来定制和初始化 EditPartViewer(关于 EditPartViewer的作用请查看前面的帖子),简单查看一下GEF的代码你会发现,在 GraphicalEdiTor类里会先后调用这两个方法,只是中间插了一个hookGraphicalViewer()方 法,其作用是同步选择和把 EditPartViewer作为SelectionProvider注册到所在的site (Site是Workbench的概念,请查Eclipse帮助)。所以,与选择无关的初始化操作应该在前 者中完成,否则放在后者完成。例子中,在这两个方法里我们配置了RootEditPart、用于创 建 EditPart的EditPartFacTory、Contents即Diagram对象和增加了拖放支持,拖动目标是当 前 EditPartViewer,后面会看到拖动源就是调色板。

这个EdiTor是带有调色板的,所以要告诉GEF我们的调色板里都有哪些工具,这是通过覆 盖getPaletteRoot()方法来实现的。在这个方法里,我们利用自己写的一个工具类 PaletteFacTory构造一个PaletteRoot对象并返回,我们的调色板里需要有三种工具:选择工 具、节点工具和连接工具。在GEF里,调色板里可以有抽屉(PaletteDrawer)把各种工具归 类放置,每个工具都是一个ToolEntry,选择工具(SelectionToolEntry)和连接工具 (ConnectionCreationToolEntry)是预先定义好的几种工具中的两个,所以可以直接使用。 对于节点工具,要使用CombinedTemplateCreationEntry,并把节点类型作为参数之一传给它 ,创建节点工具的代码如下所示。

ToolEntry tool = new CombinedTemplateCreationEntry("Node", "Create a new Node", Node.class, new SimpleFacTory(Node.class), null, null);

在新的3.0版本GEF里还提供了一种可以自动隐藏调色板的编辑器 GraphicalEdiTorWithFlyoutPalette,对调色板的外观有更多选项可以选择,以后的帖子里 可能会提到如何使用。

调色板的初始化操作应该放在initializePaletteViewer()里完成,最主要的任务是为调 色板所在的 EditPartViewer添加拖动源事件支持,前面我们已经为画布所在EditPartViewer 添加了拖动目标事件,所以现在就可以实现完整的拖放操作了。这里稍微讲解一下拖放的实 现原理,以用来创建节点对象的节点工具为例,它在调色板里是一个 CombinedTemplateCreationEntry,在创建这个PaletteEntry时(见上面的代码)我们指定该 对象对应一个 Node.class,所以在用户从调色板里拖动这个工具时,内存里有一个 TemplateTransfer单例对象会记录下Node.class(称作 template),当用户在画布上松开鼠 标时,拖放结束的事件被触发,将由画布注册的 DiagramTemplateTransferDropTargetListener对象来处理template对象(现在是Node.class ),在例子中我们的处理方法是用一个名为ElementFacTory的对象负责根据这个template创 建一个对应类型的实例。

以上我们建立了模型和用于实现视图的EdiTor,因为模型的改变都是由Command对象直接 修改的,所以下面我们先来看都有哪些 Command。由需求可知,我们对模型的操作有增加/删 除节点、修改节点名称、改变节点位置和增加/删除连接等,所以对应就有 CreateNodeCommand、DeleteNodeCommand、RenameNodeCommand、MoveNodeCommand、 CreateConnectionCommand和DeleteConnectionCommand这些对象,它们都放归类在commands 包里。一个 Command对象里最重要的当然是execute()方法了,也就是执行命令的方法。除此 以外,因为要实现撤消/重做功能,所以在Command对象里都有Undo()和Redo()方法,同时在 Command对象里要有成员变量负责保留执行该命令时的相关状态,例如RenameNodeCommand 里 要有oldName和newName两个变量,这样才能正确的执行Undo()和Redo()方法,要记住,每个 被执行过的Command对象实例都是被保存在EditDomain的CommandStack中的。

例子里的EditPolicy都放在policies包里,与图形有关的(GraphicalEditPart的子类) 有 DiagramLayoutEditPolicy、NodeDirectEditPolicy和 NodeGraphicalNodeEditPolicy, 另外两个则是与图形无关的编辑策略。可以看到,在后一种类型的两个类 (ConnectionEditPolicy和NodeEditPolicy)中我们只覆盖了createDeleteCommand()方法, 该方法用于创建一个负责”删除”操作的Command对象并返回,要搞清这个方法看似矛盾的名字 里create和delete是对不同对象而言的。

有了Command和EditPolicy,现在可以来看看EditPart部分了。每一个模型对象都对应一 个EditPart,所以我们的三个模型对象(Element不算)分别对应DiagramPart、 ConnectionPart和NodePart。对于含有子元素的EditPart,必须覆盖getModelChildren()方 法返回子对象列表,例如DiagramPart里这个方法返回的是Diagram对象包含的Node对象列表 。

每个EditPart都有active()和deactive()两个方法,一般我们在前者里注册监听器(因为 实现了 PropertyChangeListener接口,所以EditPart本身就是监听器)到模型对象,在后者 里将监听器从列表里移除。在触发监听器事件的propertyChange()方法里,一般是根据”事件 名”称决定使用何种方式刷新视图,例如对于NodePart,如果是节点本身的属性发生变化,则 调用refreshVisuals()方法,若是与它相关的连接发生变化,则调用 refreshTargetConnections()或 refreshSourceConnections()。这里用到的事件名称都是我 们自己来规定的,在例子中比如Node.PROP_NAME表示节点的名称属性,Node.PROP_LOCATION 表示节点的位置属性,等等。

EditPart(确切的说是AbstractGraphicalEditpart)另外一个需要实现的重要方法是 createFigure(),这个方法应该返回模型在视图中的图形表示,是一个IFigure类型对象。一 般都把这些图形放在figures包里,例子里只有NodeFigure一个自定义图形,Diagram对象对 应的是GEF自带的名为FreeformLayer的图形,它是一个可以在东南西北四个方向任意扩展的 层图形;而 Connection对应的也是GEF自带的图形,名为PolylineConnection,这个图形缺 省是一条用来连接另外两个图形的直线,在例子里我们通过setTargetDecoration()方法让连 接的目标端显示一个箭头。

最后,要为EditPart增加适当的EditPolicy,这是通过覆盖EditPart的 createEditPolicies()方法来实现的,每一个被”安装”到EditPart中的EditPolicy都对应一 个用来表示角色(Role)的字符串。对于在模型中有子元素的 EditPart,一般都会安装一个 EditPolicy.LAYOUT_ROLE角色的EditPolicy(见下面的代码),后者多为 LayoutEditPolicy 的子类;对于连接类型的EditPart,一般要安装 EditPolicy.CONNECTION_ENDPOINTS_ROLE角 色的EditPolicy,后者则多为 ConnectionEndpointEditPolicy或其子类,等等。

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

用户的操作会被当前工具(缺省为选择工具SelectionTool)转换为请求(Request),请 求根据类型被分发到目标EditPart所安装的EditPolicy,后者根据请求对应的角色来判断是 否应该创建命令并执行。

在以前的帖子里说过,Role-EditPolicy-Command这样的设计主要是为了尽量重用代码, 例如同一个EditPolicy可以被安装在不同EditPart中,而同一个Command可以被不同的 EditPolicy所使用,等等。当然,凡事有利必有弊,我认为这种的设计也有缺点,首先在代 码上看来不够直观,你必须对众多Role、EditPolicy有所了解,增加了学习周期;另外大部 分不需要重用的代码也要按照这个相对复杂的方式来写,带来了额外工作量。

以上就是一个GEF应用程序里最基本的几个组成部分,例子中还有如Direct Edit、属性表 和大纲视图等一些功能没有讲解,下面的帖子里将介绍这些常用功能的实现。

但是至少可以为自己的荷包省钱可以支些招,这点还是很现实的。

[Eclipse]GEF入门系列(三、应用实例)

相关文章:

你感兴趣的文章:

标签云: