网页
综合
更新时间 2026-11-13 17:51:02
A Shape Diagram Editor
自动蜘蛛
www.eclipse.org
正文约 6000 字
图形编辑框架(Graphical Editing Framework - GEF)为创建用于可视化编辑任意模型的编辑器提供了强大的基础。它的功能依赖于模块化的结构,合理选用的设计模式,和相对独立的组件,这些组件构成了一个完整的编辑器。对于一个新手来说,GEF中所涉及的大量概念和技术可能是令人难以承受的。然而,一旦这些技术被掌握并正确使用,它们就可以帮助开发出具有高扩展性和维护性的软件。本文将对GEF作相对全面的介绍。文中将描述一个几何图形编辑器作为例子-它虽然简单,但是覆盖了GEF中的核心概念。
作者 Bo Majewski, Cisco Systems, Inc. 2004年12月8日
译者 Qi Liang 2005年9月7日
绘图编辑框架(GEF)被设计用来以图形而不是文本的方式来编辑用户数据,一般被称为模型(model)。当处理包含多对多,一对多以及其他复杂关系的实体时,GEF是一种很有价值的工具。随着Eclipse Rich Client Platform 的流行,使得编辑器的开发不仅仅局限于编程,GEF的重要性也与日俱增。比如说,数据库schema编辑器 [7],逻辑电路编辑器和任务流管理器,这些例子都很好地展示了GEF是一种可以用于各个不同领域的,具有强大功能和灵活性的框架。
然而,任何通用框架都设计复杂,难于学习,GEF也不例外。到现在为止,最小的例子也将涉及75个类。即使对于最勤勉的开发者来说,要从GEF用户定义类型和GEF提供的上百种类型之间相互作用来理解GEF的独特之处,对耐心和智力的都是一种考验。为了改变这种状况,一个全新的,规模更小的编辑器例子被添加进即将到来的Eclipse 3.1(译:翻译此文时,Eclipse 3.1已经发布)。这个几何图形编辑器(看图1)允许你创建,编辑简单的图。它处理两种对象,矩形和椭圆。你可以在实线和虚线这两种连接类型中选择一种来连接两个对象。每一个连接都是有方向的,也就是说从一个源对象开始,在目标对象处终止。箭头用来表示连接方向。连接可以转移,也就是通过拖动它的源点或目标点到一个新的对象上。编辑器中的对象可以点击选中,也可以通过拖拉一个区域来选择。选中的对象可以被删除。所有的模型操作,比如添加,删除对象,移动对象,改变大小等等,都可以undo或redo。最后,编辑器集成了两个Eclipse标准视图Properties和Outline。这个编辑器的价值不是在于它的可用性,而是作为例子,通过有限的两种用户定义类型来演示在一个成熟GEF编辑器中会碰到的大多数概念和技术。
将最新的Eclipse 3.1 GEF例子从GEF项目下载页面下载下来,并解压缩至你的Eclipse目录中。按Ctrl-N,会弹出创建向导,将Examples目录展开,选择Shapes Diagram。下面给出几何图内部工作的详细的全面介绍。在我们接触代码前,我们先来看看GEF主要思想。
GEF帮助你为数据构造一个可视化的编辑器。数据可以是带有简单温度旋钮的温度调节器,也可以是一个包含几百个路由器,连接和服务质量策略的虚拟局域网。幸亏GEF设计者,他们设法建立一种框架,使得它能够和任何数据一起工作,用GEF的术语来说,就是任何模型(model)。这是通过严格遵循了模型-视图-控制器模式(MVC)来做到的。模型就是你的数据。对于GEF,模式可以是任何普通的Java对象(POJO)。模型不应该知道任何有关于控制器或视图的信息。视图(view)是模型或其某一部分在屏幕上的可视化表示。它可以是矩形,线或椭圆这样的简单图形,也可以是彼此嵌套的逻辑电路。同时,视图也应该对模型和控制器一无所知。虽然任何实现IFigure接口的类都可以作为视图,但是GEF使用Draw2D可视图形(figure)。控制器,可称为编辑部件(edit part),是模型和视图之间的桥梁。当你开始编辑你的模型时,一个顶层的控制器被创建出来。如果模型由若干个片段组成,顶层控制器就会将这个信息通知GEF。接下来,每个片段的子控制器被创建出来。如果它们又包含子片段,这个过程就会一直继续下去,直到所有组成模型的对象都有它们的控制器。控制器的另一个任务是创建可视图形来表示模型。一旦模型被设置到某个控制器,GEF就向控制器要合适的IFigure对象。既然模型和视图彼此都不知道对方,控制器负责监听模型的修改,并更新模型的可视化表示。结果,在许多GEF编辑器中,一个常见的模式就是模型发PropertyChangeEvent通知。当一个编辑部件收到事件通知时,它通过调整模型的外观或结构上的表示来作相应的改变。
可视编辑的另一个方面就是对用户动作和鼠标,键盘事件作出响应。这里的挑战在于提供一种机制,提供合理的缺省行为,并且允许重新定义行为来覆盖缺省行为,以适应所编辑模型。比如鼠标拖动事件,如果我们假设每次检测到鼠标拖动事件,所选中对象都被移动的话,我们就限制编辑器开发者的自由。很有可能有人希望在鼠标拖动的时候,提供放大,缩小的行为。GEF通过使用工具(tool),请求(request)和策略(policy)解决了这个问题。
工具是一种有状态的对象,它将象鼠标按钮被按下,被拖动等低层事件翻译成高层的由Request对象表示的请求。发送哪个请求取决于所激活的工具。例如,连接工具在收到鼠标按钮被按下这样的事件时,会发送一个连接开始或结束的请求。如果是一个创建工具,我们就会收到一个创建请求。GEF包含了大量预定义的工具以及创建应用特定工具的方法。工具可以由程序控制激活,也可以在用户实施一个动作后激活。在大多数情况下,工具将请求发送给鼠标位置下面的图形的EditPart。例如,如果你点击一个代表widget的矩形,与此相关的编辑部件就会收到一个选中请求或者直接编辑的请求。有时候,请求会发送给区域中的所有可视图形的编辑部件,比如MarqueeSelectionTool就是这样。无论一个或多个编辑部件怎样被选择为请求目标,它们自己并不处理请求。而是将这个任务交给所注册的编辑策略( edit policies)。每个编辑策略都会为该请求提供一个命令。不希望处理请求的策略将返回一个null。使用策略而不是编辑部件来响应请求的机制使得策略和编辑部件都尽可能短小,功能集中。同时,也意味着调试和维护代码变得更容易。GEF的最后一个部分就是命令(command)。GEF并没有直接修改模型,它要求你使用命令来做实际的修改。每个命令应该实现执行对模型或模型一部分的修改和撤销修改。这样,GEF编辑器自动支持模型修改的undo/redo。
除了能够提升你的技能以及设计模式方面的知识外,使用GEF的一个重要的优点在于它能够和Eclipse平台完全集成在一起。在编辑器中选中的对象可以为标准Properties视图提供属性。Eclipse向导可以用来创建,初始化GEF编辑器编辑的模型。Edit菜单中的Undo和Redo可以触发GEF编辑修改的撤销和重做。简单地说,GEF编辑器实现IEditorPart接口,是Eclipse平台中的一员,它和文本编辑器或其他workbench编辑器处于同样的集成层次。
创建GEF编辑器的第一步是创建模型。在我们的例子里,模型由四类对象组成:几何图(包含所有的图形),两种类型的图形,和图形间的连接。在我们为这些类编写代码前,我们准备了一些基础结构。
上面所列的规则对于所有模型都是相同的,为基本类建立类层次来强化这些规则是很有好处的。ModelElement类继承了Java的Object类,并提供了三个功能:持久化,属性改变和属性源支持。简单的模型持久化可以通过实现java.io.Serializable接口以及readObject方法来完成。这使得你可以将编辑器的模型以二进制格式存储。当需要和某种应用一起工作时,这并不能提供的格式的可移植性。在复杂的情况下,你需要实现将模型以XML或类似的格式存储。模型的改变通过属性改变事件来通知。这个基本类允许编辑部件注册和撤销注册为属性改变通知的接受者。属性改变通知是通过调用firePropertyChange方法触发的。最后,为了帮助和workbench的Properties视图集成,需要实现IPropertySource接口(细节在图2中忽略)。
椭圆和矩形这两类对象,在许多方面是相同的,它们的公共功能可以被提取出来放在公共类中。尤其是两者都代表着占据某个位置,具有一定大小的对象。它们可以彼此连接。这些属性的任何修改都需要通知监听者。更进一步地说,它们的位置和大小属性都可以通过IPropertySource接口暴露,这允许用户通过Properties视图来查看,和修改它们。
对象间连接的管理很值得仔细看一下。这里并没有一个全局的用于存储所有连接的地方。GEF要求模型部件报告它们之间的连接的情况,是源还是目标。这些信息都以List对象的形式提供。Shape类维护了两个数组列表,分别存储源连接和目标连接。源连接是指那些以当前图形作为源的连接,目标连接是指以当前图形作为目标的连接。两个包可见方法(和)使得图形和连接可以彼此知道相互之间的关系。此外,两个公有方法(和)使得model包外面的类知道图形的连接情况。这些方法都会被相关的图形(形状)控制器所使用,具体内容将在接下来的部分中加以介绍。
通过上面的准备,我们可以开始编写顶层模型类。Connection类表示两个图形间的连接。它存储连接的源和目标。通过调用disconnect或reconnect方法可以修改连接。连接含有一个boolean值来表示连接是否存在。命令会使用这个值来验证某种操作的合法性。源连接和目标连接都保持一个到源图形的引用,这样使得被断开的连接可以很容易地被重新连接。连接包含一个属性,就是线的类型。EllipticalShape和RectangularShape类都扩展了Shape类,添加了很少的功能。
ShapeDiagram类是ModelElement类的子类,它可以作为一种容器。它维护一组图形,并通知监听器这组图形的变化。命令可以调用addChild和removeChild方法,并检查返回的boolean值来验证它们的操作。这个类也提供了公共方法给控制器类。
细心的读者一定意识到这个模型创建了一个有向图的实现,图形作为顶点,连接作为边,所有图形,连接构成的图就是图。这里所形成的表示方式称为邻接点列表表示法,它很适合稀疏图。只要略作修改,这个模型的代码就可以转变为一般的图表示。这里对算法书中的图实现所需要做的就是添加代码使得图,节点,和边在发生改变的时候发送事件。不象数学上的图,节点不是零维的点,而是有矩形边框。最后,图存储了所有的边,而图形并没有存储连接,因为GEF并没有要求这么做。
值得注意的是,由上面的类所提供的解决方案并不是唯一的方法。那些开发计算机图形的人更愿意用另一种方法来存储连接,安排节点和边之间的通信。然而,这些细节并不是那么重要。设计者可以自由地选择他们认为更具普遍性,更快,或者功能更强的模型表示。关键的地方在模型改变的消息通知,模型修改的维护,包括对可视属性和模型持久化的支持。其余的都取决于你的经验和需要,你可以自由地进行选择。
由于这个图形编辑器非常的简单,我们不必创建可视图形来表示我们的模型,而是使用预定义的可视图形。Figure类加上FreeformLayout布局管理器用来表示图。这允许我们将对象拖放到任何位置。RectangleFigure和Ellipse都可以表示对象。使用预定义的可视图形来表示部分模型并不是通常的做法。即使你的视图没有引用模型或控制器,它都必须为每个用户可能需要查看或修改的模型重要方面都定义可视化属性。因此常常会定义拥有大量可视化属性,比如颜色,文本,嵌套可视图形等,的复杂可视图形,每个属性都对应于它们所表示的模型属性。有关创建复杂可视图形的详细处理,请参考 [4]。
对于模型的每个独立部分,我们都必须定义控制器。所谓“独立”,指的是这个实体都可以作为用户操作的对象。一个比较好的原则就是任何可以被选择,或删除的对象都应该有它自己的编辑部件。
编辑部件知道模型,监听模型改变所产生的事件,然后更新视图。由于在模型层所做的设计选择,所有的编辑部件都必需遵循图5所示的模式。每个部件都实现PropertyChangeListener接口。当它被激活时,它将自己注册为模型的属性修改事件的接收者。当失活时,它将自己从监听器的列表中移除。最后,当它收到属性修改事件时,它会根据属性名和新旧值来刷新表示模型的可视图形。事实上,这个模式使用非常普遍,在大的应用中,它会建立一个基类来提供这样的行为。
当编辑器成功载入一个几何图,并将它设置在一个图形viewer上,就要求ShapesEditPartFactory创建一个编辑部件来控制图。它创建一个新的DiagramEditPart实例,并将图设置为它的模型。当新创建的编辑部件被激活时,它将自己注册为模型的监听器,并创建一个使用free form布局管理器的可视图形,这种布局管理器允许通过它们的边界来定位图的可视图形。DiagramEditPart通过getModelChildren方法来获取图中包含的所有图形。就象前面提到的,GEF为返回的所有子模型对象都会创建编辑部件和可视图形。
DiagramEditPart类安装了三个策略。所有的策略都在AbstractEditPart类的createEditPolicies方法中定义,同时所有继承自AbstractGraphicalEditPart的实类都必需实现这个方法。编辑部件使用这些策略来处理工具发出的请求。在最简单的情况下,策略负责生成许多命令。策略使用String类型的索引字注册在编辑部件上,这个索引字被称为策略角色。这些索引字对编辑部件本身来说没有什么意义。然而,对软件开放人员,就有意义了,它使得其他人,尤其是扩展你的控制器的人,可以通过这些索引字来关闭或移除策略。就GEF而言,你的索引字可以是“foobar”。然而,你最好告诉你程序员同伴,当布局管理器改变的时候,为了设置新的布局策略,需要安装新的“foobar”策略。由于这可能很有趣,且不是那么显而易见,所以推荐你使用EditPolicy接口定义索引字,这些名字需要很好的表达该策略在编辑部件中的角色。
安装的第一个策略的索引字是EditPolicy.COMPONENT_ROLE,它负责阻止模型的根被删除。它重写了createDelete
本页为82搜索生成的网页快照,内容来自站内索引;演示数据的原文链接仅作示例。