最近在学习GEF,本例子主要是修改了八进制的例子。在这里做一下总结。先来介绍一下gef,网上关于GEF的介绍越来越多,从另一方面也说明这个技术确实在热起来,不过GEF为了实现可扩放性和可重用性,做的很是复杂(闲话一下,最近看了一本书《Better.Faster.Lighter.Java》,没看多少,Justin Gehtland, Bruce A. Tate写的,一开始就说了,为了满足越来越多的需要,各种framework,library,language都是越来越复杂了,学习曲线也变得陡峭了,这个似乎是无法避免的,java语言也是,不信去看看jdk,1.1时才3.7MB,1.4时已经38MB了。我也一直坚信,Simply the best,呵呵)。
GEF的一些资源:
1。www.eclipse.org 这个是源头啊,faq也可以看看
2。ibm的dW 除了相关文章,这里还有本关于gef和emf的红宝书,可以免费下载。
3。eclipseworld.org 这个是一个关于eclipse中文社区
4。八进制的blog http://bjzhanghao.cnblogs.com/ 。
5。这里还有些中文教程 http://www.benisoft.com/cn/index.htm
6。还有一些blog http://www.blogjava.net/hopeshared/ http://www.blogjava.net/eclipshine/ http://www.blogjava.net/reloadcn/ http://www.blogjava.net/USTCEric
好了,现在来介绍一下GEF的实现机制,主要是我的一些理解可能还有一不对的地方,欢迎大家帮我改正。
GEF是具有标准MVC(Model-View-Control)结构的图形编辑框架,其中Model由我们自己根据业务来设计,它要能够提供某种模型改变通知的机制,用来把Model的变化告诉Control层;Control层由一些EditPart实现,EditPart是整个GEF的核心部件;而 View层,大多数情况下,使用Draw2D实现了,其作用是把Model以图形化的方式表现给使用者,当然你也可以使用其他图形包,不过gef与 Draw2D的结合还是非常紧密的,一般为了使用方便,View都是使用Draw2D实现的。
关于Draw2D,八进制有一篇相应的文章,还有eclipse上的一篇文章,可以参考,这里有几点需要强调一下:
1。Draw2D提供了很多缺省图形,最常见的有三类:1、形状(Shape),如矩形、三角形、椭圆形等等;2、控件(Widget),如标签、按钮、滚动条等等;3、层(Layer),它们用来为放置于其中的图形提供缩放、滚动等功能。
2。每个图形都可以拥有一个边框(Border),Draw2D所提供的边框类型有GroupBoxBorder、TitleBarBorder、ImageBorder、 ButtonBorder,以及可以组合两种边框的CompoundBorder等等,在Draw2D里还专门有一个Insets类用来表示边框在图形中所占的位置,它包含上下左右四个整型数值。
3。一个图形可以包含很多个子图形,这些被包含的图形在显示的时候必须以某种方式被排列起来,负责这个任务的就是父图形的LayoutManager,相应的约束(Constraint)对象就在这里,比如在子图形刷新(refreshVisuals)时可以通过将其布局约束传递给其父元素来定位。
4。利用Draw2D中的Router、Anchor和Locator,可以实现多种连接样式。Router负责连接线的外观和操作方式;Anchor控制连接线端点在图形上的位置,即"锚点"的位置;Locator的作用是定位图形。
前面说过了GEF是通过MVC架构实现的,下面分别介绍这三个方面:
一、模型(Model):这个是相对比较容易理解,模型保存着业务逻辑,他是GEF这中唯一可以持久化的,并且需要提供相应的监听机制,来通知 EditPart去更新View,监听机制又可以分为分布式和集中式两种,我们可以针对域监听器进行转有通知,也可以全局广播;在这里我们认为 Command(EditPart用来更新模型的对象)也是模型的一个部分,因为它了解模型。此外,在有时候可能需要多种模型。我们对于业务模型,提供一个“视图”模型,这样便于实现和满足相应的要求,不管内部模型是怎么样的,都要实现通知机制。
二、视图。GEF提供了在EclipseWorkbench中使用Viewer,称为EditPartViewer。它的作用和jface中的 Viewer很相,不过这时候它的ContentProvider和LabelProvider是EditPart,通过setContents设置(注意在八进制代码中setContents参数为模型对象,通过分析发现,在AbstractEditPartViewer中此方法被重载,可以除了接受 org.eclipse.gef.editpart对象外可以接受Object对象,不过此时回调用EditPartFactory,通过给定的模型对象来创建相应的EditPart,个人认为这样做并不与“View与Model无直接联系”的要求相矛盾)。这个接口继承了 org.eclipse.jface.viewers.ISelectionProvider接口(这个也和jface中的viewer一样),因此使 viewer提供了监听器,这样可以将对view的操作传递到editpart。EditPartViewer会维护各个EditPart的选中状态,如果没有被选中的EditPart,则缺省选中的是作为contents的EditPart。
EditPartViewer可以适配到任何一个SWT控件,因为它要求有createControl方法,接受一个父SWT控件作为适配对象,从而将 GEF生成的Figure对象显示在这个SWT控件上,因此GEF可以使用任何图形包,不过GEF提供了对Draw2D的默认实现,比如 AbstractGraphicalEditPart类中的回调方法createFigure就要求返回一个IFigure对象。将一个GEF图形显示出来可以使用org.eclipse.ui.part.EditorPart也可以是org.eclipse.ui.part.ViewPart,如果使用 EditorPart,则需要实现继承自WorkbenchPart的createPartControl,如果是ViewPart则需要自己实现与 GEF的交互,不过Eclipse Workbench对viewpart和editorpart的支持是一样的,都是在plugin文件扩展相应的扩展点。一般来说,使用GEF对 Editorpart的模式实现是非常方便的。
GEF目前提供两种视图分别是GraphicalViewer和TreeViewer,都间接继承了EditPartViewer接口,前者利用 Draw2D图形(IFigure)作为表现方式,多用于编辑区域,后者则多用于实现大纲展示,并且他们有两个具体的实现,分别是 ScrollingGraphicalViewer和TreeViewer。其中ScrollingGraphicalViewer,通过分析GEF源代码可知,是GraphicalEditor中Viewer的默认实现,我们可以通过GraphicalEditor类提供的 setGraphicalViewer(GraphicalViewer)方法重新设置其他viewer。我们在使用的时候通过 configureGraphicalViewer和initializeGraphicalViewer方法来配置GraphicalViewer。所不同的是,在configureGraphicalViewer之后initializeGraphicalViewer之前插入了一个 hookGraphicalViewer方法,其作用是同步选择和把 EditPartViewer作为SelectionProvider注册到所在的site,使其成为SelectionProvider。
此外,GEF中还提供一种成为调色板(PaletteViewer)的视图,它也是间接实现了EditPartViewer接口。因此,我也像配置GraphicalViewer一样来配置PaletteViewer,类似的使用 configurePaletteViewer和initializePaletteViewer方法,其间类似的插入了一个 hookPaletteViewer方法,用于设置相应EditDomain的PaletteViewer。
最后,总结一下如何配置GraphicalView,主要是四个方面:
1。设置一个RootEditPart:RootEditPart的是使整个GEF框架运行起来的关键之一。RootEditPart并不对应于任何的模型对象,它将从setContents()方法中接收到的模型对象进行转换,并添加到整个的EditPart体系中去。
2。设置其EditPartFactory:负责从模型到EditPart的转换。一般来说一个模型对象对应于一个EditPart。
3。设置EditDomain:用来接收事件并选择恰当的事件处理函数进行处理,这个主要是为了相应view所接收到的用户的操作。
4。调用setContents()方法:为其设置要显示的内容。
三、控制器(EditPart)。这是GEF最核心的地方了,它将model和view联系起来。程序员可以继承三种EditPart,分别是 org.eclipse.gef.editparts.AbstractGraphicalEditPart,org.eclipse.gef.editparts.AbstractConnectionEditPart(AbstractGraphicalEditPart的子类)和org.eclipse.gef.editparts.AbstractTreeEditPart。EditPart 的生命周期当setContents时,将模型对象输入,此对象为顶层对象,通过它可以遍历所有的模型对象。:EditPartViewer配备有 EditPartFactory,EditPartViewer通过EditPartFactory构造相应的Contents EditPart。然后EditPartViewer中的每个EditPart通过EditPartFactory构造相应的子EditPart。当用户添加新的模型的时候,包含这些模型对象的所对应的EditPart将构造相应的EditPart作出相应。视图的构造和EditPart的构造是同时进行的,每当EditPart构造并添加到它的父EditPart后相应的view也做同样的事情,比如GraphicalEditor中就有 GraphicalViewer成员变量,在此edipart创建时回调用createPartControl,在这个方法中将会创建相应的view,并将其加入到父view中。
一旦用户丢弃了某些模型对象,相应的EditPart就会被丢弃。比如用户执行了删除操作,则相应的模型对象和相应的EditPart都被删除了,但用户撤销了删除操作而重新创建的EditPart与原来的EditPart是不同的,所以EditPart是不能保存长期信息的,应该交给模型去保存,并且 EditPart也是不能被Command所引用。
在具体使用时,应该先定一个Contents EditPart,它的父元素为EditPartViewer的RootEditPart。RootEditPart是EditPartView的 root,它将EditPartView和它的contents(一般认为是顶层模型对象所对应的EditPart)联系起来,RootEditPart 为用户的EditPart提供一致性的上下文(context)。并且,当没有被选中的模型EditPart时,顶层对象的EditPart是默认被选中的,而RootEditPart是不能被选中的或者定位的,甚至不能与用户相交互,他也就没有对应的模型对象。GEF提供了几个默认的 RootEditPart的实现:FreeformGraphicalRootEditPart,RootTreeEditPart, ScalableRootEditPart,ScalableFreeformRootEditPart(此类继承于 FreeformGraphicalRootEditPart,增加了缩放Zoom支持,提供了ZoomManager;增加了一个LayerPane: ScalableFreeformLayeredPane。),(原来还有个GraphicalRootEditPart,已经deprecated,用 ScalableRootEditPart代替)。这里要注意的是:
1)对于FreeformRootEditPart(以及它的子类 ScalableFreeformRootEditPart),它的Contents EditPart应该有一个org.eclipse.draw2d.FreeformFigure对象(比如FreeformLayer或FreeformLayeredPane)作为它的Figure。例如在我们的代码里,对应的就是在 DiagramPart中的createFigure方法,它返回了一个FreeformLayer对象。
2) FreeformGraphicalRootEditPart的Primery Layer没有使用draw2d的LayoutManager,并且,除非contents的figure是个freeform figure,否则不能正确的调整大小。
3)FreefromGraphicalRootEditPart使用FreeformViewport类作为它的primary figure,这个类必须和ScrollingGraphicalViewer使用。
4)ScalableRootEditPart使用 Viewport作为primery figure,这个类必须和ScrollingGraphicalViewer使用。
5)RootTreeEditPart是TreeViewer的RootEditPart。
Contents EditPart的图形一般是一个空面板,该面板将包含diagram的子图。
我们还要定义其他“视图”模型的EditPart,比如节点(node),连接(connection)。对于节点,是Graphical,可以继承 AbstractGraphicalEditPart。并且可以实现NodeEditPart接口,则可以支持源和目标的 ConnectionEditPart,使用Anchor,具体在下面有讲述。在这里,我们的代码的NodePart类中,重载了 refreshVisual方法,其中设置figure的布局约束是通过它的父元素来定位的,我不知道这是不是标准的方法,XYLayout使用的是 rectangle约束,如果长宽设为-1,表示为该图形提供理想大小。不要使用setBounds,XYLayout布局管理器可以保证对滚动条进行更新,并能将相对约束转化为绝对约束,如果长宽设置为-1,则能够将图形设置为理想的大小。此外,为了改进性能,我们可以将refreshVisual中有若干模型的分别刷新,或者设置是否需要刷新的开关。
对于连接,可以继承AbstractConnectionEditPart,这个抽象类已经实现了 ConnectionEditPart。和Node实现一样,通过refreshVisual方法将属性从模型映射到图形。连接也可以有约束,不过与前面的约束不尽相同。在这里,连接路由器(connection router)使用约束来是连接转向(bend)。需要注意的是,一个Connection EditPart的图形必须是一个draw2d的Connection,这样就引入了连接锚(Connection Anchor)。连接必须由连接锚(ConnectionAnchor)“锚定”在两端。因此,需要在连接EditPart或者节点EditPart中实现使用什么样的锚。缺省情况下,GEF使用节点EditPart通过实现NodeEditPart接口来提供锚。这是因为,锚的选择取决与各个节点上正在使用的图形,而连接EditPart不应了解节点上的图形,并且,当用户创建连接的时候,连接EditPart并没有创建,这时候就由节点自己显示反馈。这里需要注意的是:何时使用以及何时不使用ConnectionEditPart,当用户可以选择某些东西并可与之交互时,就需要 ConnectionEditPart。它可能与某行中的某个对象相对应,并且回自己删除;如果这是需要一个绘制直线的节点或者容器,那么只需要用图形的 paint方法绘制直线,或者组合一个图形,这个图形包含Polyline图形。连接必须要拥有源和目标。如果要创建一个连接,并且在没有源和目标存在的情况下,可以继承AbstractGraphicalEditPart,并使用连接图形,比如PolylineConnection。
正如前面说的,模型需要实现某种通知机制,各个EditPart要将自己做为监听者,注册在模型上,以便在模型发生改变的时候收到相应的通知。 当EditPart收到相应的通知时,调用相应的处理方法,来进行一次强制的刷新,对于模型对象的增减可以调用refreshChildren方法,对于简单属性的更改可以调用refreshVisuals方法,这时,像前面提到的,可以只刷新改变的部分,以提高性能。一般,我们在Activate方法中注册监听器,在Deactivate方法中注销监听器。
以上完成的是从模型到视图的映射,你还需要实现的是编辑策略Edit Policies,因为EditPart并不直接对模型进行编辑,每一个EditPolicy都针对一个特定的编辑任务或者一组相关的工作。这样的实现便于在不同的EditPart间重用(reuse)编辑行为,并且这样的实现,可以动态的改变行为,比如改变layout或者routing时。在 createEditPolicies方法中,我们要insall我们的的Policies,这时候就需要roles,roles的作用就是作为关键字来标示不同的policies。policy中提供Command,每个应用程序都有个Command栈,Command的运行要通过Command栈,而不是直接调用execute方法。
说到这里,要注意EditPartView是视图的,一般放在一个UI插件上比如EditorPart,viewPart,甚至swt控件。EditPart是控制器,联系视图和模型。
在本文的最后介绍一下GEF的运行机制:EditPartView接受用户的操作,如节点的移动,增删,每个节点对应一个EditPart对象,每个 EditPart都有一组由Role区分的EditPolicies,每个EditPolicy会对应一些Command对象,Command最终会对模型进行操作。用户的操作会转化为Request分配给特定的EditPolicy,由EditPolicy创建相应的Command对模型进行操作,这些 Command会保留在EditDomain的命令堆栈中,用于实现undo,redo功能,EditDomain是专门用于维护 EditPartView,Command信息的对象,一般每个EditPart有一个EditDomain。
以上就是对所作的GEF部分的大概描述,可能有些地方写的比较罗嗦,有的地方写的也可能不对,希望大家指出,谢谢。下面还要对RCP部分进行一些描述。