浅谈Eclipse GEF

2023-05-15 10:30:30 digiproto

相信学过编程的同学都会用过Eclipse,Eclipse作为一个集成开发环境(IDE),给我们带来了不错的开发体验(虽然我知道大家都用IDEA了),但是Eclipse的作用却不仅限于此。


Eclipse本身是一个框架平台,由于众多插件的支持,使得Eclipse拥有较佳的灵活性,所以许多软件开发商以Eclipse为框架开发自己的IDE。如果你的想开发属于自己的IDE,那么选择Eclipse作为你的开发框架是个不错的选择。随着业务变得越来越复杂以及可视化的需求越来越多,如果你想在基于Eclipse平台的IDE中画个UML图,流程图,工作流 ,那么应该怎么去实现呢?


GEF(Graphical Edit Framework)是Eclipse官方提供的一个图形编辑框架,它允许开发人员以图形化的方式展示和编辑模型,从而提升用户体验,基于GEF框架开发的应用有很多种类型,例如:UML 类编辑器、图形化XML编辑器、界面设计工具以及图形化数据库结构设计工具等等。归结一下,可以发现它们在图形化编辑方面具有以下共同之处:


  • 提供一个编辑区域和一个工具条,用户在工具条里选择需要的工具,以拖动或单击的方式将节点或连接放置在编辑区域;

  • 节点可以包含子节点;

  • 用户能够查看和修改某个节点或连接的大部分属性;

  • 连接端点锚定在节点上;

  • 提供上下文菜单和键盘命令;

  • 提供图形的缩放功能;

  • 提供一个大纲视图,显示编辑区的缩略图,或是树状模型结构

  • 支持撤销/重做功能;

  • 等等。


图片关键词

▲用GEF编写的流程编辑器


GEF是标准的MVC(Model-View-Control)框架,开发人员可以利用GEF来完成以上这些功能,而不需要自己重新设计。与其他一些MVC编辑框架相比,GEF的一个主要设计目标是尽量减少模型和视图之间的依赖,好处是可以根据需要选择任意模型和视图的组合,而不必受开发框架的局限。不仅如此,GEF框架中也包含着许多经典设计模式,最突出的是Command模式,方便的实现编辑器中的Undo/Redo功能等等。无论是从业务还是学习,GEF都会是一个值得学习的框架。下面我会从GEF框架的核心组建和一次挪动后GEF框架内的工作流程来给大家讲讲GEF框架。


GEF中的核心组件


图片关键词

▲GEF结构图


模型(Model)


GEF中的模型只与控制器(Editpart)打交道,而不知道任何与视图(View)有关的内容。为了能让控制器知道模型发生的变化,应该把控制器作为事件监听者注册在模型中。这样当模型发生变化时,就会触发相应的事件给控制器,控制器会负责通知各个视图进行更新。


下面来看一下代码是如何实现的,首先在模型类中需要加入PropertyChangeSupport类型的成员变量来维护监听器成员即控制器,如下示例代码。

PropertyChangeSupport listeners = new PropertyChangeSupport(this);
public void addPropertyChangeListener(PropertyChangeListener l) {
   listeners.addPropertyChangeListener(l);
}
protected void firePropertyChange(String prop, Object old, Object newValue) {
   listeners.firePropertyChange(prop, old, newValue);
}
public void removePropertyChangeListener(PropertyChangeListener l) {
   listeners.removePropertyChangeListener(l);
}


那么控制器作为事件监听者如何加入到这个监听列表里面呢?在控制器类初始化的时候,会去调用active()方法,在该方法内去注册监听即可。

public void activate() {
   if (isActive()) {
       return;
   }
   super.activate();
   ((Node) getModel()).addPropertyChangeListener(this);
}



当模型发生变化时,可以调用firePropertyChange()方法去通知控制器发生改变。


典型的模型对象不仅会包含监听者列表,对于与其他对象具有连接关系的模型,要维护连入(Inputs)/连出(Outputs)的连接列表;如果模型对象的节点具有大小和位置信息,也需要维护它们。这些变量可能并不是业务模型本身必须的信息,维护它们使得模型变得不够清晰,但是你可以通过构造一些抽象模型类来维持它们的可读性。总结一下,模型其实是根据我们的业务去自由设计的,但真实的GEF模型不仅仅包含着业务模型有关的数据,其中还包含着坐标、大小一些图形信息,这个值得我们去注意。

接下来我们讲控制器(EditPart)。


控制器(EditPart)


与一般MVC框架中的Controller不同,控制器在GEF中叫做Editpart。控制器是模型与视图之间连接的桥梁,也是整个GEF应用的核心组件。它不仅监听着模型发生的变化,当用户编辑视图时,还要把编辑器结果反应到模型上。 每一个模型都会对应一个Editpart对象,一个个EditPart对象组合起来共同完成了控制器的作用。


图片关键词

▲EditPart对象


那么怎样去操作EditPart呢,GEF中将用户的编辑行为转化为了一系列请求(Request),然后将请求交给控制器去处理。这倒与SpringMVC有着异曲同工之妙,SpringMVC也是在Controller中接收HTTP请求然后进行处理的。GEF中的请求种类是非常多的,有针对布局的一系列请求,如CreateRequest(创建节点请求)、ChangeBoundsRequest(改变节点大小请求),还有针对连线的一些列请求等等。如果在EditPart对象中统一处理这些请求的话,那么它的类需要非常大了。为了解决这种情况,GEF将这些请求进行了分类,针对某一类请求,可以加入处理这一类请求的“特殊控制器”。这些请求种类在GEF里被称为角色(Role),而这种“特殊控制器”被称为编辑策略(EditPolicy)。


如下面代码所示,EditPart类中会有createEditPolicies()方法,该方法可以让你针对控制器的不同自由的去安装这些编辑策略,有点类似插件的概念。这样做的直接好处就是可以在不同的EditPart之间共享一些重复的操作。

PropertyChangeSupport listeners = new PropertyChangeSupport(this);
public void addPropertyChangeListener(PropertyChangeListener l) {
   listeners.addPropertyChangeListener(l);
}
protected void firePropertyChange(String prop, Object old, Object newValue) {
   listeners.firePropertyChange(prop, old, newValue);
}
public void removePropertyChangeListener(PropertyChangeListener l) {
   listeners.removePropertyChangeListener(l);
}


接下来控制器接收到了请求之后,会做什么呢?接下来就到了最为关键的部分。编辑策略(EditPolicy)接收到请求后,根据请求创建相应的命令(Command),命令会直接操作模型对象。如下面的实例代码,是一个创建模型的请求。

protected Command getCreateCommand(CreateRequest request) {
   if (request.getNewObject() instanceof Node) {
       CreateNodeCommand cmd = new CreateNodeCommand();
       cmd.setDiagram((Diagram) getHost().getModel());
       cmd.setNode((Node) request.getNewObject());
       Rectangle constraint = (Rectangle) getConstraintFor(request);
       cmd.setLocation(constraint.getLocation());
       return cmd;
   }
   return null;
}


基本已经把控制器这一部分讲完了,如果还有些地方不理解,可以根据下面图再去理解一下控制器的通信流程。下面讲视图。


图片关键词

▲通信链(请求-控制器-命令)


视图(View)


GEF中的视图是依赖于Draw2D框架的,其作用是把Model以图形化的方式表现给用户。Draw2D是一个图形框架,在这里有关Draw2D的知识我就不讲了,有兴趣可以上网搜一下。我们这里讲一下GEF是如果引用Draw2D的。如下面示例代码,在控制器中,有这么一个抽象方法让我们去创建模型的视图。


protected abstract IFigure createFigure();


工具(Tool)


我们已经知道控制器是通过请求去操作的,那么是什么生成请求的呢?那就要说到工具(Tool)了。GEF的工具可以分为很多种,如MarqueeSelectionTool(框选工具)、SelectionTool(选择)等等。这个工具其实跟Photoshop中的工具库是同一个概念,我们可以选择不同的工具或者创造自己的工具去操作模型。工具会接收到用户的鼠标键盘事件(KeyBoardEvent、MouseEvent),不同的工具接收到事件后生成不同的请求。


编辑器(EditorPart)


我们在Eclipse中如果想使用GEF框架,必须要依靠Eclipse提供的编辑器(EditorPart)。编辑器作为Eclipse和GEF的纽带,作用非常大。在编辑器中里不仅仅包含了模型的根节点、视图、Actions等等。你可以在编辑器中配置快捷键,实现保存和读取文件的功能等等。具体这里就不展开讲了。有兴趣的可以搜一下Eclipse RCP的框架平台。


编辑域(EditDomain)


编辑域也是GEF中重要的一个组件,它的源码中是这样描述的。


| The collective state of a GEF "application", loosely defined by a CommandStack, one or more EditPartViewers, and the active Tool. An EditDomain is usually tied with an Eclipse IEditorPart. However, the distinction between EditorPart and EditDomain was made to allow for much flexible use of the Graphical Editing Framework.


它的作用是为了解耦与编辑器的一些属性。如果将GEF里面所有的对象和内容都放在编辑器中,那么这个类会变得很大,属性很多,所以GEF将其解耦出来了。编辑域中包含着所有工具的集合、视图的集合还有最重要的是它包含着命令栈(CommandStack)。命令栈里面包含着所有GEF中执行过的命令(Command),通过命令栈的push和pop,可以实现一系列命令的撤销和重做功能。


挪动一次节点,GEF中发生了什么


图片关键词

▲GEF各组件交互图


基本的组件和概念已经讲完了,接下来我们通过讲述挪动一次模型,GEF中发生了什么来回顾一下之前讲的内容。

1.首先用户会通过鼠标去操作模型,Eclipse接收到鼠标事件(MouseEvent)后交给编辑域(EditDomain)去处理;


2.编辑域根据鼠标事件去选择相对应的工具,挪动模型会用到GEF中DragEditPartsTracker这个工具;


3.这个工具接收到鼠标事件后,根据事件发生的坐标生成一个改变位置的请求(Request),然后将请求转交给控制器(EditPart)处理;


4.控制器接收到请求后,会在自己对象里面找到一个处理这个请求的编辑策略(EditPolicy),让编辑策略去处理请求;


5.编辑策略接收到请求后,根据请求的信息去产生一个命令(Command),这里命令包含着改变模型位置的数据;


6.产生命令后,会存储在编辑域(EditDomain)中的命令栈(CommandStack)中,并且执行;


7.命令(Command)执行后,修改模型里面有关位置的数据。当模型位置数据发生变化时,控制器作为模型的监听者接收到了模型的变化;


8.控制器(EditPart)监听到模型位置发生的变化后,改变了视图(View)的位置。

好了,Eclipse GEF就讲到这里,相信大家对GEF这个框架有了一定的认识,下次有机会再讲讲GEF的实践部分吧。

标签: Eclipse GEF
首页
产品
新闻
联系