GEF学习

关键词:Eclipse,RCP,GEF
作者:BIce 创建时间:2013-02-16 23:07:29

 

最近做项目需要有一个图形编辑的工具,由于之前用过Eclipse RCP开发,也对GEF有一些了解,就选择了这个平台,不过后来发现GEF的学习曲线真的是很高,想大致了解能用得上也不是容易的事,最近终于做完了一部分,就把需要注意的地方记下来.

1.介绍

GEF(Graphical Editing Framework)是一个图形编辑器框架,它可以允许我们做出像UML编辑器这种工具。GEF依赖于Eclipse平台,其开发主要依赖于Eclipse插件技术,而网络上的教程或者官方例子都是基于插件的实现,没有作为RCP程序的,这次做的图形编辑器期望做到相对独立的样子,所以采用RCP程序整合GEF应用。

GEF采用完全的MVC框架,也就是在使用GEF的时候,必须遵循MVC的设计理念进行编码,不过这样也导致了有一些设计模型的使用,如:工厂模式,职责链模式,观察者模式等,这是GEF学习曲线比较高的主要原因。

GEF中的MVCModel可以是普通POJO类,ViewDraw2dFigure,(创建由Controller负责),ControllerEditPart类。需要说明的是GEF使用Draw2d进行说明View的展现,而View的Figure都是处于Draw2d的不同Layer,一般模型元素对应的Figure处于Primary Layer中,而连接的Figure处于Connection Layer中,Primary Layer与Connection Layer属于Printable Layer,包含所有可以打印的内容。而一些特殊的Figure,如反馈和Handler则处于特殊的Feedback Layer和Handle Layer中。

2.开始GEF

在进行GEF相关开发之前,首先需要在你进行开发的Eclipse里安装GEF的相关插件,安装方法可以用Eclipse->Help->Install New Software的方式来安装,安装地址可以在http://www.eclipse.org/gef/updates/index.php 中找到。

在完成GEF的安装之后,可以通过Eclipse->New->Example->GEF找到示例程序,其中有一个Shape Diagram Editor比较完整,关于这个示例程序,在Eclipsewiki上有大致的介绍,还是很不错的,其英文版:http://www.eclipse.org/articles/Article-GEF-diagram-editor/shape.html ,中文版:http://www.eclipse.org/articles/Article-GEF-diagram-editor/shape_cn.html  

使用GEF之前,一定在要使用GEF的项目中(一般为Plug-in 或者RCP)plugin.xml文件中加入一个Dependencyorg.eclipse.gef (如果找不到的话,就是没有正确安装好GEF插件,再好好看看安装是否正确),之后就可以在项目中使用GEF技术啦。

3.使用GEF

3.1 GraphicalEditorWithPaletteEditorPart,一切的入口

要使用GEF,一般我们期望构建的的应用都是类似UML编辑器这样的应用,这种应用一般都有一个Palette,一个编辑区。这种典型的Editor GEF中已经给出了定义:GraphicalEditorWithPalette,这是一个EditorPart的子类,一般我们继承这个类就可以,我们继承这个类之后,就可以和一般的EditorPart一样,在应用中进行使用了:

(一般是在一个ViewPart中使用:

editor=getSite().getWorkbenchWindow().getActivePage().openEditor(

new NormalFileInput("D:\test.gef"), /

YourEditor.ID);

打开Editor)

GraphicalEditorWithPalette之中,有两个Viewer:PaletteViewerGraphicalViewer,分别负责编辑器的左右两部分,其中GraphicalViewer是需要仔细设置的编辑区。

 

而在继承了GraphicalEditorWithPalette的子类中,我们需要注意重写的有如下几个函数:

Ø 构造函数:在构造函数中,我们必须设置其EditDomain,一般使用默认的EditDomain即可,代码如下:

setEditDomain(new DefaultEditDomain(this));

Ø Init函数:负责Editor的初始化,一般要设置inputsite,一般代码:

@Override

public void init(IEditorSite site, IEditorInput input)

throws PartInitException {

// Initialize the editor part

setSite(site);

setInput(input);

 

diagram=new Diagram(); //这个是需要自己定义的Model,作为GraphicalViewerContents,在后面会介绍

 

//其他的初始化工作

...............

}

Ø getRootPalette函数:负责获取Editor所使用的Palette,一般代码:

@Override

protected PaletteRoot getPaletteRoot() {

if(this.paletteRoot==null){

//创建PaletteRoot对象

paletteRoot=YourPaletteFactory.createPalette();

//在createPalette函数中,需要进行Palette的初始化工作,在此处也就定义了用户在Palette可以使用的工具(就是左边面板里的东西了啦),这个YourPaletteFactory需要自己完成,在后面会介绍

}

return paletteRoot;

}

ØconfigureGraphicalViewer函数:负责在GraphicalViewer初始化,接收到Content之前对其进行配置,一般代码为:

@Override

protected void configureGraphicalViewer() {

super.configureGraphicalViewer();

GraphicalViewer viewer = getGraphicalViewer();//得到GraphicalViewer

viewer.setEditPartFactory(new YourEditPartFactory());//设置GrahpicalViewerEditPartFactory,这里用到了工厂模式,用于生成指定模型的控制器,在后面会介绍

viewer.setRootEditPart(new ScalableFreeformRootEditPart());//设置GraphicalViewerRootEditPart,此EditPart采用默认的EditPartRootEditPart不与实际的模型关联,它负责管理其他所有的EditPart

 

// configure the context menu provider,如果需要Editor加入上下文菜单的话,需要在此处设置

 

}

Ø initializeGraphicalViewer函数:负责在GrahpicalViwer被创建之后,初始化其内容,一般代码为:

@Override

protected void initializeGraphicalViewer() {

GraphicalViewer viewer = getGraphicalViewer();

viewer.setContents(diagram); // set the contents of this editor

//设置GraphicalViewerContent,也就是上面提到的diagram,此对象作为一个其他所有对象的根对象,由它来维护所有GraphicalViewer中出现的其他Model,在后面介绍

}

 

3.2 YourPaletteFactory: PaletteViewer的内容

在上面的GraphicalEditorWithPalette中的getPaletteRoot方法中,我们用到了YourPaletteFactory.createPalette方法,创建了PaletteViewer的内容,下面就简单介绍下此方法。

我们可以在Palette中见到的内容一般有几项:

1. Select/Marquee这种工具,用来选择元素;

2. 可以在Editor中创建的元素:图形;

3. 图形直接的连线

 

这几种元素都要在createPalette方法中创建,并加入到PaletteRoot对象中。下面就对这几种元素的创建一一举例:

 

Ø Select/Marquee:一般是作为ToolEntry加入到PaletteToolBar中,再加到PaletteRoot对象中

Ø 可创建的模型元素:一般是作为CombinedTemplateCreationEntry先加入到PaletteDrawer中,再加入到PaletteRoot对象中。其中CombinedTemplateCreationEntry对象会指定:创建工具的文本,创建模型的类,创建工具所使用的工厂类,创建工具的图标等等

Ø 模型元素之间的连线:一般是作为ConnectionCreationToolEntry加入到PaletteDrawer中,加入到PaletteRoot中,不同的是,它的创建工厂要重载getNewObject()getNewObjectType()两个方法来识别具体连线的类别

 

当然,只是初始化PaletteRoot的话,只能在Palette中显示出相应内容,而如果想要Palette的操作能有对应的行为的话,还需要EditPart的支持(Palette Tool 想要应用到哪个Model上,就要在其Model对应的EditPart中加入支持.),在后面介绍。

 

上面仅是简单介绍,具体代码参考官方示例程序中的ShapeEditorPaletteFactory

3.3 YourEditPartFactory

在用户在Palette中选择了需创建Model的操作之时,需要一个创建此Model对应的控制器:EditPart的工厂类,此类在configureGraphicalViewer方法中已经设置(看上面)。

这个工厂类需要继承EditPartFactory类,而我们一般要重写的方法是:

Ø createEditPart:为一个Model创建其对应的EditPart,并为将两者关联起来,示例代码

@Override

public EditPart createEditPart(EditPart context, Object model) {

// get EditPart for model element,需要将所有Model与EditPart的对应关系都像如下代码写下来才可以

EditPart part = null;

if (modelElement instanceof Diagram) {

return new DiagramEditPart();

}

if(modelElement instanceof Group){

return new GroupEditPart();

}

// store model element in EditPart

part.setModel(model);

return part;

}

 

3.4 ModelElement

说完了EditPartFactory,再说说GEF Model,由于GEF采用完全的MVC模式,所以尽管理论上Model可以是任意的POJO类,但是一般为了简化操作,我们需要所有的Model都支持观察者模式:也就是在Model有修改之时。需要通知它的观察者——一般就是Model对应的EditPart

为此,我们一般观察者模式的实现为一个抽象类ModelElement,在其中维护一个成员:

private transient PropertyChangeSupport pcsDelegate;

用以维护注册到其上的观察者(一般EditPart也要对应的实现PropertyChangeListener接口

 

Model上的所有Setter方法,都要使用

firePropertyChange(EVENT_PROPoldValuenewValue);

方法来通知注册在其上的观察者进行事件处理。

 

而为了简化说明,本总结中只介绍三中实际的Model,都是ModelElement的子类它们分别是:Diagram,负责维护所有编辑器的ModelShape,代表可显示图像的ModelConnection,代表Shape的连线的Model,下面依次说明。

3.5 Diagram

一般我们创建GEF应用时,或者看网上的教程时,一般都会实现一个Diagram类,用于作为GraphicalViewerContentinitGraphicalViewer中设置,看上面),来维护在Editor中所有其他的Model,作为顶层的根模型。

 

当然这个对象里面一般都有一个列表,用于存储其子模型。而在其子元素变化的时候,需要通知其观察者DiagramEditPart进行处理。

 

3.6 Shape

代表一个有具体图形的Model元素,它的一般成员有:

 

//位置

private Point location=new Point(0,0);

 

//size信息

private Dimension size=new Dimension(20,20);

 

//inputConnections和outputConnections用于维护模型上的链接,所有链接都可以使用其进行管理,其成员为Connection的实例

 

/** List of outgoing Connections. 出度*/

protected List inputConnections = new ArrayList();

/** List of incoming Connections. 入度*/

protected List outputConnections = new ArrayList();

 

//表示属性的常量字符串

public static final String LOCATION_PROP="Shape.location";

 

public static final String SOURCE_CONNECTIONS_PROP="Shape.sourceConnections";

public static final String TARGET_CONNECTIONS_PROP="Shape.targetConnections";

 

其属性的Setter方法一般除了设置属性值之外,还要触发事件,通知注册在它上的观察者进行事件处理。

 

3.7 Connection

代表连线的Model,它的一般成员有:

/**关系的源点*/

private Shape source=null;

 

/**关系的终点*/

private Shape target=null;

 

/**是否连接到了两端的End Point*/

private boolean isConnected=false;

 

/**默认构造函数,为了让SimpleFactory可以使用newInstance反射方法创建实例*/

public Connection();

 

/**链接两个Shape*/

public Connection(Shape s, Shape t);

 

/**执行链接动作,要触发firePropertyChange*/

public reconnect();

 

public reconnect(Shape s, Shape t);

 

/**解开链接,要触发firePropertyChange*/

public disconnect();

 

具体代码见官方实例ShapesConnection类。

 

4. EditPart

说到MVC模式,Controller是非常重要的部分,GEFControllerEditPart,它承担了GEF MVC的大部分工作,甚至包括了View的内容。和Model相对的,EditPart一般要实现PropertyChangeListener,作为Model的观察者。

 

在描述EditPart之前,首先要说一下GEF操作处理的流程。用户使用GEF编辑器之时,对于Editor的操作会产生Tool,这些Tool会被GEF翻译成Request,而这些Request会发送到操作对应的EditPart之上,由EditPartEditPolicies中合适和EditPolicy来进行响应,产生适用的Command来对Model进行修改,Model被修改之后,会发生firePropertyChange调用通知给它对应的EditPartEditPart得到属性修改事件之后,会调用合适的更新View的函数(refreshVisuals();refreshSourceConnections();refreshTargetConnections();),完成View的更新,完成用户操作的处理过程。

 

(而Role就是封装一系列Request的集合. EditPartinstallEditPolicy就是以Role为单位,GEF中内置了很多的定义好的RoleRequestEditPolicy。祥见GEF SDK.

 

可见EditPart是一个MVC模式的中心,而一个EditPart的内容主要包含如下几个部分:

 

1.//创建Model对应的View IFigure对象

@Override

protected IFigure createFigure();

 

2.//用于在创建EditPart之时,将EditPart于Model关联起来,加入Model的监听队列

/**加入监听队列*/

@Override

public void activate() {

if (!isActive()) {

super.activate();

((ModelElement) getModel()).addPropertyChangeListener(this);

}

}

/**离开监听队列*/

@Override

public void deactivate() {

if (isActive()) {

super.deactivate();

((ModelElement)getModel()).removePropertyChangeListener(this);

}

}

 

3.//用于创建EditPart对应的EditPolicies

@Override

protected void createEditPolicies();

 

4./**处理事件响应,Model.firePropertyChange函数..的修改*/

@Override

public void propertyChange(PropertyChangeEvent evt)

 

5./**作为存有子模型的元素,必须重载此方法返回自己的子模型,否则子模型无法正常展现,像本总结中的EditPart中,DiagramEditPart就必须重写这方法*/

@Override

protected List getModelChildren()

 

6./**刷新视图的函数, 当模型位置修改,大小变动的时候会调用这个函数,一般模型都要重写这个函数*/

@Override

protected void refreshVisuals();

 

一般用以显示模型的元素EditPart可以继承于AbstractGraphicalEditPart,而连线的EditPart可以继承AbstractConnectionEditPart,来简化自己的代码。

4.1 DiagramEditPart

作为Diagram模型的EditPart,继承于AbstractGraphicalEditPart,像刚才说过的,它是负责维护Editor所有显示元素的模型的EditPart。它的简要实例代码如下:

@Override

protected IFigure createFigure() {

//创建Layer并设置Layout

Figure f = new FreeformLayer();

f.setBorder(new MarginBorder(3));

f.setLayoutManager(new FreeformLayout());

 

// 创建ConnectionLayer的静态路由

ConnectionLayer connLayer = (ConnectionLayer) getLayer(LayerConstants.CONNECTION_LAYER);

connLayer.setConnectionRouter(new ShortestPathConnectionRouter(f));

 

return f;

}

 

@Override

protected void createEditPolicies() {

//Policy 1:安装此EditPolicy,阻止本EditPart被删掉(Root成员不可删除)

installEditPolicy(EditPolicy.COMPONENT_ROLE,new RootComponentEditPolicy());

 

//Policy 2:本EditPolicy在下面给出,是一个内部类,它有两个作用:

// 1)处理Create Request,也就是在新建一个Model的时候,向Diagram中加入一个Shape到拖放的位置

//    2)处理Constraint Change Request,也就是对已有Shape进行 resize或move的操作

installEditPolicy(EditPolicy.LAYOUT_ROLE,new ShapesXYLayoutEditPolicy());//一个自己实现的类,用于处理模型的Create和Move/Resize操作,祥见官方实例程序

}

 

/**处理事件响应*/

@Override

public void propertyChange(PropertyChangeEvent evt) {

/**事件的属性名*/

String prop = evt.getPropertyName();

//当ShapesDiagram中有加入或者删除Child的属性时,重新刷新其Children

if (Diagram.CHILD_ADDED_PROP.equals(prop)

|| Diagram.CHILD_REMOVED_PROP.equals(prop)) {

 

DebugLogTool.log("DiagramEditPart handle Child add or remove prop");

refreshChildren();

 

}

 

}

/**作为存有子模型的元素,必须重载此方法返回自己的子模型,否则子模型无法正常展现*/

@Override

protected List getModelChildren() {

return ((Diagram)getModel()).getChildren(); // return a list of shapes

}

 

4.2 ShapeEditPart

显示图像模型对应的EditPart,继承于AbstractGraphicalEditPart,它的代码如下

/**刷新视图的函数*/

@Override

protected void refreshVisuals() {

// notify parent container of changed position & location

// if this line is removed, the XYLayoutManager used by the parent

// container

// (the Figure of the ShapesDiagramEditPart), will not know the bounds

// of this figure

// and will not draw it correctly.

Rectangle bounds = new Rectangle(((Shape)getModel()).getLocation(),

((Shape)getModel()).getSize());

((GraphicalEditPart) getParent()).setLayoutConstraint(this,

getFigure(), bounds);

}

 

/**属性修改处理, 观察者模式的回调事件*/

@Override

public void propertyChange(PropertyChangeEvent evt) {

String prop = evt.getPropertyName();

if (Shape.LOCATION_PROP.equals(prop)) {

 

DebugLogTool.log("Receive Location Change Event");

refreshVisuals();

else if (Shape.SOURCE_CONNECTIONS_PROP.equals(prop)) {

refreshSourceConnections();

else if (Shape.TARGET_CONNECTIONS_PROP.equals(prop)) {

refreshTargetConnections();

}

}

/**显示View*/

@Override

protected IFigure createFigure() {

Ellipse e=new Ellipse();

//e.set

e.setOpaque(true); // non-transparent figure

e.setBackgroundColor(ColorConstants.lightBlue);

return e;

}

 

ShapeEditPart的createPolicies函数比较复杂,因为构建连线的事件需要在ShapeEditPart中处理,而连线分为两步(选个源点,然后选目的点)。(具体代码见官方实例Shape

// allow the creation of connections and

// and the reconnection of connections between Shape instances. 允许创建连接,或者重新连接图形元素

installEditPolicy(EditPolicy.GRAPHICAL_NODE_ROLE,

new GraphicalNodeEditPolicy() {

//Create Connection Step 1

protected Command getConnectionCreateCommand(CreateConnectionRequest request) 

//Create Connection Step 2

protected Command getConnectionCompleteCommand(CreateConnectionRequest request)

//Reconnection Source

protected Command getReconnectSourceCommand(ReconnectRequest request)

//Reconnection Target

protected Command getReconnectTargetCommand(ReconnectRequest request)

};

 

而且为了支持连线,Shape需要进行一些设置,大致如下

protected ConnectionAnchor getConnectionAnchor() {

if (anchor == null) {

anchor = new ChopboxAnchor(getFigure());

}

return anchor;

}

 

//获取SourceConnection,用于刷新连线

@Override

protected List getModelSourceConnections() {

return getCastedModel().getSourceConnections();

}

 

//获取TargetConnection,用于刷新连线

@Override

protected List getModelTargetConnections() {

return getCastedModel().getTargetConnections();

}

 

//获取锚点

@Override

public ConnectionAnchor getSourceConnectionAnchor(ConnectionEditPart connection) {

return getConnectionAnchor();

}

@Override

public ConnectionAnchor getSourceConnectionAnchor(Request request) {

return getConnectionAnchor();

}

 

@Override

public ConnectionAnchor getTargetConnectionAnchor(ConnectionEditPart connection) {

return getConnectionAnchor();

}

 

@Override

public ConnectionAnchor getTargetConnectionAnchor(Request request) {

return getConnectionAnchor();

}

 

4.3 ConnectionEditPart

表示连接的ConnectionEditPart,继承于AbstractConnectionEditPart,默认的情况,Connection不需要处理事件的响应的话,就不需要做什么工作,但是如果要处理删除等动作的话,就需要在createEditPolicies方法加入代码:

 

protected void createEditPolicies() {

// Selection handle edit policy.

// Makes the connection show a feedback, when selected by the user.

installEditPolicy(EditPolicy.CONNECTION_ENDPOINTS_ROLE,

new ConnectionEndpointEditPolicy());

// Allows the removal of the connection model element

installEditPolicy(EditPolicy.CONNECTION_ROLE,

new ConnectionEditPolicy() {

protected Command getDeleteCommand(GroupRequest request) {

return new ConnectionDeleteCommand(getCastedModel());

}

});

}

 

而在createFigure方法中要给出连线的View

protected IFigure createFigure() {

PolylineConnection connection = (PolylineConnection) super

.createFigure();

connection.setTargetDecoration(new PolygonDecoration()); // arrow at

// target

// endpoint

connection.setLineStyle(getCastedModel().getLineStyle()); // line

// drawing

// style

return connection;

}

 

5. 关于连线

基本的GEF信息已经简单记录了一下,下面主要记录下关于连线的方面。

 

连线一般用于处理模型元素之间的关联关系,在上面说的实例中,Connection没有作为Children加入到Diagram中,而是作为ShapeConnections属性存储,关于Connection的视图刷新主要是通过Shape元素的getModelSourceConnections()getModelTargetConnections()来进行处理的,而ConnectionView显示也不与Shape相同,是放在和Diagram不同的一个Layer中的。这应该就是GEF中默认的处理Connection的方式。

 

当然Connection也可以用和Shape类似的方法实现,存储到Diagram中,再通过修改createFigure()等方法就可以了,不过难度应该比较大,也没试过,应该可以通过默认的方式找到替代方案的吧。

 

注:学习GEF的话推荐八进制的博客

 http://read.pudn.com/downloads154/ebook/680691/GEF%E5%85%A5%E9%97%A8%E7%B3%BB%E5%88%97.pdf

以及GEF的Wiki http://wiki.eclipse.org/GEF

 

留言功能已取消,如需沟通,请邮件联系博主sunswk@sina.com,谢谢:)