GEF简介与事件机制

一、GEF简介
GEF(Graphical Editing Framework)是一个图形化编辑框架,它允许开发人员以图形化的方式展示和编辑模型,从而提升用户体验。
GEF的优势是提供了标准的MVC(Model-View-Control)结构,开发人员可以利用GEF来完成以上这些功能,而不需要自己重新设计。与其他一些MVC编辑框架相比,GEF的一个主要设计目标是尽量减少模型和视图之间的依赖,好处是可以根据需要选择任意模型和视图的组合,而不必受开发框架的局限(实际上视图基本是Draw2D的实现)。

二、GEF各项目的关系图:


图2.1


1、 Draw2D:建立了二维的图形库(树状图形部件Figure),负责显示二维的图形展示。

2、GEF:(Graphical Editing Framework)建立MVC构架,代码利用Draw2D作为自己的View部分,主要代码实现复杂的树状(于Model分别对应)的控制器。实现的框架具有很高的可复用等特性,例如:将图形部件功能分解为多个EditPolicy,这样使用者可以通过installEditPolicy接口来定制,以及扩充自己的某一功能特征。

3、EMF:(Eclipse Modeling Framework)首先在一个轻量级项目以及项目初期开发,Model常常采用拥有Property的Java对象来简单明了表示【即 Plain Old Java Objects (POJOs)】。EMF定义了一套Ecore元模型,使得Model具有易于维护、易于扩充、易于数据交换等特性的Model Framwork。因此、在实际中大型的项目中往往会采用Draw2D+GEF+EMF的方式来实现整体的MVC模式。例如:ROSE的最新版本 IBM.Rational.Software.Architect就是采用了这种构架。

4、GMF:(Graphical Modeling Framework )使用了GEF和EMF,建立了两个之间的桥梁框架,同时实现了一些领域上的典范应用,如UML图,甘特图,脑图等。(项目正在进行中这些功能还未完备。可以用来参考以实现自己的一个标准的GEF+EMF的应用)。

5、各项目依赖关系:

 
图2.2


如上图:使用者可以有以下几种用法:
EMF: Model管理层
Draw2D: 二维图形展现
Draw2D + GEF: 二维图形的简单编辑模块
Draw2D + GEF + EMF: 二维图形的编辑模块
Draw2D + GEF + EMF + GMF:基于GMF提供的图形编辑功能之上扩展

三、Draw2D技术
1、Draw2D是什么?
轻量级框架:轻量级框架,使用LightweightSystem类把SWT和Draw2D 连接起来,它把鼠标事件和画图事件从SWT 转发给Draw2D的图形上。它是Draw2D的核心类。
Draw2d中的Graphics和GC的对比:
Graphics类的很多方法和GC的完全一样,尤其是绘制线和几何图形、显示图片以及字体等方面。但Graphics还提供了一个不同于GC的功能:它的对象可以在LightweightSystem中移动。这意味着当你想要改变一个图形组件的位置时,Draw2d提供了它自己的拖拽机制来将一个Figure移动到合适的位置。
2、 LightweightSystem 主要包含三个组成部件:
LightweightSystem不依赖于特定操作系统,这意味着你失去了SWT/Jface重量级组件的优势,比如快速的响应和本地外观风格,但你得到的,是对自己的组件的外观与行为的完全控制。
1).根图形【IFigure】: 根图形是RootFigure类的实例,应用程序的根图形必须建立在它之上。它继承了一些SWT Canvas的图形环境,如颜色,字体等。
2).事件转发器【EventDispatcher】: 事件转发器把SWT事件转换成相应Draw2D事件。它可以跟踪那个图形被聚焦,那个图形是被鼠标点击的目标图形。它还控制tooltip的激活。它为想捕捉鼠标的图形提供支持。
3).更新管理器【UpdateManager】:更新管理器是负责画和更新Draw2D图形。当一个画的请求被事件转发器从SWT canvas发送到LightweightSystem上时,LightweightSystem调用更新管理器performUpdate()方法。一般管理器维持着一个List列表,该列表包含无效的或需要重画的图形。
3、Figure类
Draw2D的界面中看到的都是图形,即Figure类的实例。它在一个重量级的窗口中模拟一个重量级图形系统。在不消耗太多系统资源情况下,他们允许你创建一个复杂的图形展示。图形都应该建立在根图形之上。
Figure的绘制过程:
1).paintfigure() 图形绘制本身
2).paintclientarea() 绘制该图形的子图形
3).paintborder() 绘制该图形的修饰边框
验证:当图形的大小或位置需要被计算时(如图形的位置发生变化时),Draw2D将使用验证。
1).validate( ) 要求图形的布局管理器重新布局它的子图形。
2).revalidate( ) 首先调用invalidate( )方法(该方法会使该图形及其父链上的图形无效),其次,添加该图形及其父链到更新管理器的无效List上。

四、GEF的MVC
1、GEF的优势是提供了标准的MVC(Model-View-Control)结构。与其他的一些MVC框架相比,GEF的一个主要设计目标是尽量减少模型和视图之间的依赖,好处是可以根据需要选择任意模型和视图的组合,而不必受开发框架的限制。

2、 GEF中MVC框架的实现


图4.1 MVC的示例图




图4.2 GEF的MVC实现




图4.3 GEF通过EditPart来管理Model和Figure


通过第二章的内容,结合图4.2中我们可以得到一个结论:
View部分:GEF利用Draw2D的Figuer作为自己的View。
Model部分:往往内部包含了千差万别、不可预知的商业信息。因此GEF框架应该尽量减少约束。
Controller部分: EditPart中建立了与其对应的Draw2D的Figure和用户自定义的Model结点建立一一关联的关系。
Controller(EditPart)也是树形的层级,这样的话更能容易的对具体具有树形结构的Model,进行特殊化UI和相关的行为处理方式,更容易进行功能分解。
因此在设计Controller(EditPart)时,只需处理自身逻辑、管理子Controller、通知父Controller。

3、 如何使用GEF来创建Model/Figure/EditPart



图4.4 GEF创建MVC相关类


1).从上图可以看出在GEF是通过EditPartFactory根据以存在的Model 来创建并设置不同的EditPart。
EditPartFactory的接口如下:
EditPart createEditPart(EditPart context, Object model);

2).其中model是Object类型,因此可以看出在GEF对model没什么过多的约束,context是于要创建的EditPart有关联的EditPart,例如是将要创建EditPart的Parent。
参考时序图:GEF创建Editor时序图

五、EditPart
1、EditPolicy

六、GEF的事件转义与事件传递:Tool
1、用户输入(系统UI事件)与UI业务语义
在界面操作过程中,我们希望面对的是边框调整事件,图形移动事件,或者是编辑文本事件,而不是原始的鼠标和键盘事件?


术语定义:
系统UI事件:一般由某些计算机人机交互硬件发出信号,并且经操作系统理解后产生的事件,例如:原始的鼠标的移动、点击和键盘的敲击事件等。
UI业务语义事件: 由系统UI事件和其它UI业务语义事件触发,由UI业务代码理解后产生的事件(操作系统不可理解,同时无任何领域的业务语义),如2D图形的尺寸调整,2D图形的点击,2D图形的移动。(其实不仅限于事件的转义,包括UI的改变,也需要由系统UI语义转换到UI业务语义来使用)
UI业务语义事件在Controller控制中的优点是易于理解、维护和扩充,具体的主要有以下方面:
a.减少系统UI事件判断逻辑部分代码的重复。比如在Controller部分代码无需分步着大量逻辑重复的代码,当鼠标移动时去判断究竟是一个2D图形的尺寸改变,还是一个2D图形的移动,还是一组2D图形的移动,还是2D图形DragAndDrop的动作。
b.Controller部分的代码语义明确。在Controller内部处理代码和接口中直接面对的是UI业务语义,如图形的移动,图形的尺寸改变,这样、代码易于理解、调试等。
b.Controller部分代码无需为系统UI事件保存状态。如2D图形的移动中,如果这些事件直接在Controller中处理,从开始MouseDown,到MouseMove,到MouseUp过程中,在Controller中要集中保留这样众多状态,极易造成Controller代码具体多头职责的症状,从而患上严重的精神分裂:)。

2、GEF给出的解决方案



图6.1 GEF给出的解决方案


GEF通过Tools(根interface是Tool),当然在视图上的Menu和Toolbar是通过Action发出,这个是eclipse的Workbench机制,在此就不讨论了。


图6.2 Tool的类继承树


从Tool继承的这些类均处理一个或者一类UI业务语义事件,其中需要保留中间状态的子类均以XXXTracker形式出现。
首先这些Tool的实现类通过Tool的接口(上图右边),接收界面来的事件,然后调用内部方法,例如键盘按下事件(AbstractTool. handleButtonDown())之类的handle***()的方法;这样通过AbstractTool的具体实现类中的内部函数getTargetRequest()[createTargetRequest()]和getSourceRequest()[createSourceRequest()],把系统UI事件转换为UI业务语义事件叫Request(如图6.3)。至此Tool会通过调用相关联的EditPart的接口将UI业务语义事件通知到EditPart中。于是在Controller(EditPart)中仅仅需要理解处理Request对象。


图6.3 Request的类继承树


Tool这些事件接口,如何从Viewer传递过来的呢?

图6.4 从SWT的系统UI Input到GEF的Tool


注意以下两方面
1)、创建这些关键事件处理链的过程(注意图上的粗体文字);
2)、消息从SWT中Canvas实例传递到Tool过程(图D中注释标签的顺序)

学习了Tool产生Request的机制,可以帮助我们调试GEF,同时如果在大家自己的程序中需要有新的UI交互类型,也可以试着建立自己的Tool类产生新的Request,这样在自己的EditPart产生对应的Command,这样就可以处理实现新的UI交互类型。

自定义Request
从本章的内容,理解了SWT的UI输入如何传到GEF的Tool中,并知道Too是用来对系统UI输入进行转义并转换成EditPart能理解的Request,而最终会由EditPart的EditPolicy来处理Tool产生的Request。
那么我们就可以针对GEF的机制来制定我们自用的Request来实现我们特定的功能。

假设我们需要有一个功能,当通过Tool,左键画布上选中的节点,并改变节点的颜色。

1)、修改Node,增加用于反映颜色的变量
模型里节点(Node)类里没有反映颜色的成员变量,所以先要在Node类里添加一个color属性,以及相应的 getter/setter方法,注意这个setter方法里要和其他成员变量的setter方法一样传递模型改变的消息。仿照其他成员变量,还应该有一个静态字符串变量,用来区分消息对应哪个属性。
final public static String PROP_COLOR = "COLOR";

protected RGB color = new RGB(255, 255, 255);

public RGB getColor() {
    return color;
}

public void setColor(RGB color) {
    if (this.color.equals(color)) {
        return;
    }
    this.color = color;
    firePropertyChange(PROP_COLOR, null, color);
}

2)、修改NodePart支持当Node的color属性的值改变时进行视图的刷新
修改NodePart里的propertyChanged()和 refreshVisuals()方法,在前者里增加对color属性的响应,在后者里将NodeFigure的背景颜色设置为Node的color属性对应的颜色。
public void propertyChange(PropertyChangeEvent evt) {
    if (evt.getPropertyName().equals(Node.PROP_COLOR))//Response to color change
        refreshVisuals();
}
}

protected void refreshVisuals() {
((NodeFigure) this.getFigure()).setBackgroundColor(new Color(null, node.getColor()));
}

3)、自定义一个Request
因为目的是改变颜色,所以不妨叫做ChangeColorRequest。它应当继承自org.eclipse.gef.Request,我们需要ChangeColorRequest上带有两样信息:1.需要改变颜色的节点;2.目标颜色。因此它应该有这两个成员变量。
public class ChangeColorRequest extends Request{
    final static public String REQ_CHANGE_COLOR="REQ_CHANGE_COLOR";
    private Node node;
    private RGB color;

    public ChangeColorRequest(Node node, RGB color) {
        super();
        this.color = color;
        this.node = node;
        setType(REQ_CHANGE_COLOR);
    }

    public RGB getColor() {
        return color;
    }

    public Node getNode() {
        return node;
    }

    public void setNode(Node node) {
        this.node = node;
    }

    public void setColor(RGB color) {
        this.color = color;
    }
}
ChangeColorRequest看起来和一个JavaBean差不多,的确如此,因为Request的作用就是传递翻译后的鼠标事件。如果你看一下org.eclipse.gef.Request的代码,你会发现Request还有一个type属性,这个属性一般是一个字符串(在gef的RequestConstants里预定义了一些,如RequestConstants.REQ_SELECTION_HOVER), EditPolicy可以根据它决定是否处理这个Request。在我们的例子里,顺便定义了这样一个常量字符串REQ_CHANGE_COLOR,在后面的 ChangeColorEditPolicy里会用到它。

4)、需要覆盖SelectionTool的handleButtonDown
(PS:尽量做到复用,GEF中已经提供了很多很好用的Tool的实现类,我们通过对GEF中Tool的类继承树中具体实现类的理解来选择比较符合我们当前需要实现功能的Tool实现类,并覆盖其中方法来达到代码复用,也能更快的实现功能。)

我们理解了对节点上色这个需求后,发现“上色工具”的用法和“选择工具”基本上差不多。显然,我们需要覆盖的是handleButtonDown()方法,用来告诉gef如果用户当前选择了这个工具,在画布区域按下鼠标会发生什么事情。
代码如下:
public class ChangeColorTool extends SelectionTool {.
private RGB color;

   public ChangeColorTool(RGB color) {
        super();
        this.color = color;
    }

   @Override
    protected boolean handleButtonDown(int button) {
        //Get selected editpart
        EditPart editPart = this.getTargetEditPart();
       
        if (editPart instanceof NodePart) {
            NodePart nodePart = (NodePart) editPart;
            Node node = (Node) nodePart.getModel();
           
            //Create an instance of ChangeColorRequest
            ChangeColorRequest request = new ChangeColorRequest(node, color);
           
            //Get command from the editpart
            Command command = editPart.getCommand(request);
           
            //Execute the command
            this.getDomain().getCommandStack().execute(command);
           
            return true;
        }
        return false;
    }
}

5)、实现改变颜色的Command
GEF里任何对模型的修改都是通过command完成的,因此一个ChangeColorCommand肯定是需要的。
public class ChangeColorCommand extends Command{
    private RGB oldColor;

    @Override
    public void execute() {
        oldColor = node.getColor();
        node.setColor(color);
    }
   
    @Override
    public void undo() {
        node.setColor(oldColor);
    }
}

6)、增加Policy来处理ChangeColorRequest
EditPolicy负责接收所有的Request,所以还要创建一个ChangeColorEditPolicy。
public class ChangeColorEditPolicy extends AbstractEditPolicy {
    final static public String CHANGE_COLOR_ROLE = "CHANGE_COLOR_ROLE";

    @Override
    public Command getCommand(Request request) {
        //Judge whether this request is intersting by its type
        if (request.getType() == ChangeColorRequest.REQ_CHANGE_COLOR) {
            ChangeColorRequest theRequest = (ChangeColorRequest) request;
           
            //Get information from request
            Node node = theRequest.getNode();
            RGB color = theRequest.getColor();
           
            //Create corresponding command and return it
            ChangeColorCommand command = new ChangeColorCommand(node, color);
            return command;
        }
        return null;
    }
}

7)、在NodePart中增加ChangeColorEditPolicy
protected void createEditPolicies() {
   //Add change color editpolicy
installEditPolicy(ChangeColorEditPolicy.CHANGE_COLOR_ROLE,new ChangeColorEditPolicy());
}

猜你喜欢

转载自aquarion.iteye.com/blog/2101203