GEF 应用初学

目的:实现在editor上画矩形,并将矩形用线连接起来。进一步,在GEF中实现属性视图、大纲视图。

创建例程

利用模板创建一个Hello RCP插件例程,运行该例程(这个例程是用了eclipse的平台的启动入口)。

添加GEF依赖插件

在该例程的plugin.xml的插件依赖项中,目前只有org.eclipse.uiorg.eclipse.core.runtime两个插件依赖项,现在将org.eclipse.gef插件添加进来。

扩展editor

打开“扩展”选项卡,对org.eclipse.ui.editors扩展,

<extension

         point="org.eclipse.ui.editors">

      <editor

            class="gef.chenq.editor.GEFEditor"

            contributorClass="gef.chenq.actions.DiagramActionBarContributor"

            default="true"

            extensions="chenq"

            icon="icons/alt_window_16.gif"

            id="gef.chenq.gefEditor"

            name="gef Editor">

      </editor>

   </extension>

说明:class 指明了我们GEF Editor,可以理解为画布。

contributorClass:使我们对于键盘、菜单等进行动作的绑定而设定。

extensions:是我们定义的文件的后缀名,当文件的后缀是”chenq”,双击该文件就能打开GEF编辑器。

定义了class就要实现它,因为我们要使这个editor有一个调色板,所有让GEFEditor继承GraphicalEditorWithPalette类。这个类就是我们编辑器类。

对于contributorClass,也要实现它,暂时让它继承gef中的ActionBarContributor。在buildActions()以及contributeToToolBar()方法中添加redo/undodelete的动作(参考其他)。

定义模型

根据要求我们要创建3个模型,一个是画布Diagram,一个是代表矩形的Node,一个是代表连线的Connection。因此,在models包下建立这三个模型。GEF告诉我们模型要与控制器EditPart打交道(EditPart是模型和视图之间交互的唯一桥梁)。模型的变化如何通知EditPart呢?我们让EditPart(每一个模型对应一个控制器EditPart)实现propertyChangeListener接口,然后将控制器作为监听器注册到模型上,当模型变化时,控制器EditPart就收到通知。为了同一监听器注册机制,我们令这三个模型继承Element这个抽象类,这个类负责注册监听器和删除监听器。并且当模型变化时,负责通知所有的监听器。

Node

这个类表现在编辑器中时,有名字,有位置,还作为连接源和目的。因此它有4个属性,namelocationinConnectionsoutConnections。各个属性的setaddremove方法内一定含有调用通知editpart的语句。

Diagram

这个模型表现在界面上是一片空白,它有子图形NodeConnection不算),因此,Diagram只有一个Node属性的列表。

Connection

这个类表现为一条直线,因此它有源Node和目的Node两个属性。它的属性变化就不需要通知editPart,它构造时就应该有了sourcetarget两个Node

创建EditPart控制器

上面说了,每一个模型都对应一个控制器,因此在这里创建三个editPartNodePartDiatramPartConnectionPart。这些控制器都直接或间接继承AbstractGraphicalEditPart类(参考相关代码)。别忘了上面说的,让editPart实现PropertyChangeListener接口。

DiagramPart

在创建代表模型的图形方法createFigure()中我们返回一个GEF自带的图形FreeformLayer,让后给这个figure设置一个layoutManager。我们就不用构造一个figure了。

createEditPolicies()方法中,安装到EditPart中的每一个Policy都对应一个ROLE,因为Diagram有子元素,一般地都会安装EditPolicy.LAYOUT_ROLE,我们安装Diagram的编辑策略。

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

因为Diagram含有子元素,我们需要覆盖方法getModelChilden(),返回Diagram内的子模型。

Diagram内可以增加和删除子元素,因此在propertyChange()方法中,要处理子元素增加和删除时的响应,refreshChilden();

每个EditPart内都有activate()deactivate()方法,因此,当editpart激活时我们将editPart作为监听器注册到model上,当不活动时,移除editpart。因此当model改变时,editPart能监听器事件。

ConnectionPart

ConnectionPart需要继承AbstractConnectionPart,作为连线的控制器,在createFigure()方法中,我们可以使用gef内的PolylineConnection类,而不必自己构造一个figure

createEditPolicies()方法中,对于连接类型,一般要安装EditPolicy.CONNECTION_ENDPOINTS_ROLE角色的编辑策略。

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

installEditPolicy(EditPolicy.CONNECTION_ENDPOINTS_ROLE, new ConnectionEndpointEditPolicy());

我们可以删除连线,因此安装删除策略;我们可以在两个图形之间画线,因此安装ConnectionEndpointEditPolicy

NodePart

我们自己构造一个NodeFigure(实现一个NodeFigure继承RectangleFigure),在createFigure()方法中,返回一个NodeFigure的实例。

createEditPolicy()中安装node的删除策略,安装一个NodeGraphicalNodeEditPolicy,该策略是为了让连接可以添加到node上来(实现4个方法)。

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

        installEditPolicy(EditPolicy.GRAPHICAL_NODE_ROLE, new NodeGraphicalNodeEditPolicy());

propertyChange()方法中,需要监听node位置改变,名称改变,连接进入和连接输出等属性的变化。

位置等变化时需要在refreshVisuals()刷新Node的显示位置和名称。

同样的,需要在activatedeactivate方法中注册和删除监听器EditPart

为了让NodePart能用线连接,作为连接的源和目的,nodePart还要实现NodeEditPart接口,在4getXXXAnchor()中返回chopboxAnchor(getFigure())

要显示连线,NodePart还需要覆盖方法getModelSourceConnectionsgetModelTargetConnections方法,要不然,我们的连线,在创建的时候看见,一旦放开鼠标,连纤就看不见了。因为界面要不断的刷新连接的起点和终点。getModelSourceConnectionsgetModelTargetConnections方法暗地里在refreshSourceConnectionsrefreshTargetConnections方法内调用。

创建编辑策略

在上面各个editpart中的createEditPolicies()中,我们需要安装编辑策略,Role-EditPolicy——command的这种模式主要是为了尽量重用代码,同一个editPart中可以安装不同的policy,同一个command中可以被不同的policy重用。

DiagramLayoutEditPolicy

该类继承XYLayoutEditPolicy是一个表示XY布局的编辑策略,需要实现方法getCreateCommand(),在这方法中返回创建Node的命令CreateNodeCommand实例。

为了拖动Node节点在编辑器中移动,还要覆盖createChangeConstraintCommand()方法,在该方法中创建移动Node的命令MoveNodeCommand

NodeGraphicalNodeEditPolicy

创建了node节点,下面就要创建连线,所以该类继承自GraphicalNodeEditPolicy类,实现4个方法,在创建连接命令和连接创建完成命令两个方法中需要对创建连接命令CreateConnectionCommand构造和处理。

DeleteNodeEditPolicy

这个类很明显是创建一个删除Node节点命令的编辑策略。继承自ComponentEditPolicy,需要覆盖createDeleteCommand().方法需要返回一个删除节点的命令。

DeleteConnectionPolicy

功能同上。

创建命令

在编辑策略中,我们返回了一系列的创建、删除等命令,这些命令直接操作模型。这些命令一般都继承自gef.commands.Command类。

CreateNodeCommand

创建Node节点,该类具有Node属性、diagram属性、以及nodelocation属性。所谓创建节点,就是在该类的execute()方法中向Diagram模型中添加node.并指定node显示位置的location

GEF中都有redo/undo的操作,因此我们让这些命令类都覆盖Command中的redo/undo方法。

DeleteNodeCommandMoveNodeCommand

同上。

CreateConnectionCommand

创建连接命令。查看DiagramLayoutEditPolicy中,我们知道Node节点模型在创建“创建节点命令”之前的createRequest参数中就能够取得。而这个Connection模型不需要添加到diagram中,在CreateConnectionRequest参数中没有Connection模型,因此在execute()中创建connection模型。在鼠标移动并进入Node节点之上时,NodeGraphicalNodeEditPolicy中的getCreateConnectionCommand()和getCreateConnectionCommand()方法都会调用,创建了大量的Command,这些命令最终是不是入栈,需要看该条命令是不是被执行了。在上面两个方法中,需要给CreateConnectionCommand命令设置源端和目的端。因为鼠标的滑动可能创建大量的command,这就要求在CreateConnectionCommandcanExecute()方法中做一定的判断。

DeleteConnectionCommand

同上。

创建图形Figure

figure在编辑器中代表一个模型,用于可视化。上面提到了Diagram的图形,使用gef中的freeformLayerConnection的图形使用PolylineConnection。而nodeNodeFigure需要我们自己创建。

NodeFigure

我们用一个矩形框框来代笔一个Node,继承RectangleFigure,在该类内部定义一个Label(是一个Figure)作用是显示Node的名字。该类用名字属性,需要将名字设置给label。还要覆盖setBounds()方法,并设置label的参数设置给label

创建编辑器

在扩展扩展点时,我们声明了一个GEFEditor类,下面要填写代码实现之。该类中最重要的两个方法是configureGraphicalViewer()和initializeGraphicalViewer(),用来定制和初始化editPartViewer,在GraphicalEditor中先后调用者两个方法,不过中间加了一个将editor注册作为selectionProvidersite的方法hookGraphicalViewer()。我们需要在这两个方法中配置rootEditPartEditPartFactoryContents

RootEditPart和模型没有关系,是一种特殊的EditPart,可以认为是contents的容器。

EditPartFactory负责给定模型对象创建对应的EditPart对象(以显示),该类被Editor使用。比如在拖拽创建节点时,当Node模型生成了,并添加到Diagram模型后,DiagramPart收到结构改变的属性变化就去刷新childen,这时候就要去通过这个EditPartFactory创建这个nodeEditPart

Content指的就是Diagram这个模型。

setInput()Editor设置input,如果我们再GEFEditor中的总模型对象Diagram没有初始化,就要在这里初始化了。要不然报空指针了。

GEFEditor带有一个调色板,需要实现getPaletteRoot()方法创建调色板并组织调色板内的组件内容。通过一个工具类PaletteFactory来创建。创建几个PaletteContainer类分类调色板内的工具,对于选择图形工具、连接图形工具,采用gef中的SelectionToolEntry以及ConnectionCreateToolEntry实现。而node节点工具通过CombindTemplateCreateENtry生成。

GEFEditor支持拖动,因此需要在initializeGraphicalViewer()方法中添加拖动释放目标监听器,该监听器DiagramTemplateTransferDropTargetListener需要我们自己实现,并覆盖getFactory()方法,返回一个工厂类ElementFactory,(我们可以不必自己创建而采用SimpleFactory),ElementFactory负责创建各种模型。另外,我们还要对调色板添加拖动源监听器(gef.TemplateTransferDragSourceListener)。

实现键盘监听和菜单绑定

参考《动作重定向RetargetAction.doc》资料。

上面已经完成了基本GEF的实现,可以创建图形和连线。但是按delete键,却不能删除图形和连接,ctrl+Zctrl+y也不能实现redo/undo,这是因为我们没有将全局键绑定到GEFEditor中。在扩展editor扩展点时,有一项ContributorClass,就是来做这个事情的。redo/undo,delete动作GEF内部已经定义好了,我们需要在DiagramActionBarContributor中的buildActions()方法中使用addRetargetActionnew UndoRetargetAction())。如果还想将这几个动作绑定到toolBar上,还需要覆盖contributeToToolBar()方法,使用toolBarManager.add(getAction(ActionFactory.UNDO.getId()));……

 

如果只是一些常用的动作redo/undo,我们可以不用再buildActions()方法中再去创建,而是可以直接在declareGlobalActionKeys()方法通过调用addGlobalActionKey()来绑定。这里面隐含的是redo/undo操作都已经创建了,其实这些操作是在GEF编辑器的createActions()方法中创建的(通过super.createActions())。

 

如果我们不想通过定义一个actionBarContributor的类来重定向动作。那么我们可以在GEFEditor初始化的时候添加action重定向代码来完成,比如,在initializeGraphicalViewer()方法中通过getEditorSite().getActionBars()得到actionBars,并通过getActionRegistry()方法得到actionRegistry

//准备从ActionRegistry中找actionId对应的处理动作

        ActionRegistry registry = getActionRegistry();

        //actionBar上按钮的重定向

        IActionBars bars = this.getEditorSite().getActionBars();

        IToolBarManager toolBarManager = bars.getToolBarManager();

        //actionid

        String id = ActionFactory.UNDO.getId();

        bars.setGlobalActionHandler(id, registry.getAction(id));

        //动作绑定到工具栏上

        toolBarManager.add(registry.getAction(id));

 

实现Figure的直接编辑

实现Node的重命名的直接编辑。

实现直接编辑的原理:当用户发出修改请求时,就在文字所在的位置覆盖一个文本框等组件作为编辑器,编辑结束后,在将编辑器中的内容应用到模型中。

GEF中这个弹出的编辑器由DirectEditManager类负责管理,在NodePart类里,通过覆盖performRequest()方法响应用户的DirectEdit请求,在这个方法内构造一个DirectEditManager类的实例。其参数有editpPart控制器,有弹出的编辑器的类型(TextCellEditor.class)以及cellEditor的位置cellEditorLocator实例,然后让manager显示(show)。

根据以上要求,首先要实现一个DirectEditManagerNodeDirectEditManager,这该类内主要实现initCellEditor()方法,为编辑器中的text设置初始值、字体、颜色、选中全部文字等操作。

然后要实现一个CellEditorLocatorNodeCellEditorLocator,该类的方法relocate()使得当编辑器内容改变时,让编辑器始终处于正确的位置,为此,在方法内取得cellEditorText的最优size以及nodeFigure的文本的边界,计算后设置给text

修改NodeFigure,添加一个方法getTextBounds()并在内返回NodeFigurelabel的文本的边框label.getTextBounds().如果返回的是label.getBounds(),那么弹出的编辑器的文本框将会在NodeFigure左上角(因为labelboundfigurebound大小一样)。

准备工作完成了,下面还是老生常谈,建立直接编辑的Editpolicy安装到NodePart中,建立DirectNodeEditCommand添加到NodeDirectEditPolicy中。其中有showCurrentEditValue()方法,作用是在编辑时,始终保持figure中的文本和编辑器中的文本一致,不至于出现盖不住的情况。

实现属性视图

比如要在属性视图中显示Node的基本属性需要修改几处,1首先要将属性视图所在的插件添加到依赖项(org.eclipse.ui.views)2 node实现IPropertySource接口,实现方法getPropertyDescriptors()需要返回变量对应的描述。3 实现getPropertyValue(),4 实现setPropertyValue()。

实现大纲视图

eclipse中当编辑器Editor被激活时,大纲视图自动通过这个编辑器的getAdapter()方法寻找他提供的大纲(大纲实现IContentOutlinePage接口)。GEF提供了ContentOutlinePage类(是一个page)用来实现大纲视图(PageBookView)。我们要做的就是重点实现它的createPartControl()方法。大纲视图(PageBookView)有一个PageBook,包含很多的page,并且可以在他们之间切换。切换的依据就是当前活动的editor,因此我们在createPartControl()中就是要构造这个pagepage中的createPartControl()如下:

publicvoid createControl(Composite parent)

        {

            // getViewer()返回的是在构造函数中的TreeVieweroutline就是一个Tree

            outline = getViewer().createControl(parent);

            // TreeViewer添加到同步器中,从而让用户不论在大港或是编辑区内选择editPart

            // 另一方都能自动做出同样的选择

            getSelectionSynchronizer().addViewer(getViewer());

 

            // 保存命令的栈,与GEFEditor中的EditDemain是一个,这样能在大纲视图中和GEFEditor中都能响应redo/undo动作

            getViewer().setEditDomain(getEditDomain());

            // 要有一个editpartfactory负责给定模型对象创建对应的editPart对象

            getViewer().setEditPartFactory(new TreePartFactory());

            // 将模型(内容)和视图联系起来

            getViewer().setContents(diagram);

        }

同时,在GEFgetAdapter()方法中当传入的参数时IContentOutlinePage.class时返回创建的outlinePage对象。

我们大纲视图的实现也是采用MVC的模式,所以给TreeViewer(也就是getViewer())设置一个模型到控制器的创建工厂TreePartFactory。在我们实现的TreePartFactory中,我们新建了两个在大纲视图中应用的EditPartDiagramTreeEditpartNodeTreeEditPart)。

这基本完成大纲视图的实现,操作发现,当大纲视图处于激活状态时,toolBar上的redo/undo不可用,这是因为对于大纲视图,没有进行action的重新绑定,因此,在outlinePage类的init()方法中添加action重绑定的代码。

猜你喜欢

转载自sdfx.iteye.com/blog/1940907