eclipse插件开发:GEF 进阶: Viewer

 

Viewer是GEF中顶层的界面组件,可以认为Viewer就是一块画板,里面放什么东西完全可以由你控制。在GEF中,这样的画板不止一块,其外观也不太相同,我们也可以添加自己的Viewer。Viewer在内部应用了MVC的设计模式,要自定义一个Viewer,必须完成MVC的所有元素,本文演示了这个基本的过程。
ViewerGEF中的一些常见的组件其实都是Viewer,如下图所示:
图 1. GEF中一些缺省的Viewer图1列出了GEF中一些常见的Viewer,可以看出来它们的外观上有不小的差异,但是它们在本质上都是一样的。它们都实现了EditPartViewer接口,并且都是MVC模式的。我们不要被它们的外观所蒙蔽。
上面提到的EditPartViewer接口是GEF中的Viewer必须实现的一个接口,这是一个较为庞大的接口。仔细的浏览其方法,可以粗略的了解到Viewer的一些能力,比如拖放支持,上下文菜单,键盘事件等等。从这个接口派生出了很多类,但是基本上可以分为两类:有画板支持和无画板支持的。比如从AbstractEditPartViewer派生出来两个子类:GraphicalViewerImpl和TreeViewer。GraphicalViewerImpl也就是我们通常进行可视化编辑的那块区域。而TreeViewer则是图1中Outline视图显示的内容,在Outline视图中,我们没有办法进行所见即所得的编辑,也就是我说的“没有画板支持”。
GEF自带的一些Viewer已经可以满足我们大部分的需要,如果你有一些特殊的需求,需要实现一个特别的Viewer,也可以很容易的做到。本文的其余部分就来添加一个简单的Viewer,逐步的解释添加Viewer时需要了解的概念和注意的问题。

 

自定义Viewer我们打算在shapes示例代码的基础上,在编辑区域添加一个Viewer,这个viewer的基本功能就是按顺序显示所有的图形,但是不显示连线,同时在选择其中的图形的时候,主编辑区域的图形也会被选择。
模型层由于Viewer是MVC架构的组件,因此要添加一个自定义的Viewer,比如兼顾MVC的所有元素,首先是模型层。幸运的是,我们不需要自定义什么模型,当然你可以这样做,不过在本文的例子中,我们沿用shapes示例代码中的模型。
表示层从现有的类中继承是快速实现自定义Viewer的方法,我们可以从ScrollingGraphicalViewer继承出我们自己的Viewer,如下所示:
清单1. 继承ScrollingGraphicalViewerpublic class ShapeViewer extends ScrollingGraphicalViewer {   protected void hookControl() {      super.hookControl();      FigureCanvas canvas = getFigureCanvas();      canvas.getViewport().setContentsTracksWidth(true);      canvas.getViewport().setContentsTracksHeight(false);      canvas.setHorizontalScrollBarVisibility(FigureCanvas.AUTOMATIC);      canvas.setVerticalScrollBarVisibility(FigureCanvas.NEVER);   }}
我定义了一个ShapeViewer作为我们的表示层,这个类很简单,只覆盖了父类的hookControl()方法。hookControl()本质上只是做一些初始化工作,比如配置一下滚动条。我们可以覆盖或者添加更多的方法,但是我并不打算把它弄的很复杂,为了方便我们理解这个过程,代码越少越好。
控制层控制层是工作相对多的一块,首先的一个问题是:我们可以不可以重用shapes示例中已有的那些EditPart呢?大部分代码是可以重用的,因为我们这个Viewer也是有画板支持的,所以本质上也都应该继承自AbstractGraphicalEditPart。但是有些代码,比如连线相关的代码,我们就不需要,因为我们不支持显示连线。还有一些EditPolicy相关的代码,则要看你的具体需要了,如果需要相关的角色,则可以重用。为了简单起见,我们使用了FlowLayout来安排图形,因此我们为Diagram添加了FlowLayoutEditPolicy。因为我们希望代码越简单越好,所以我们不支持创建Command,全部设为返回null。
由于这部分的代码基本上是shapes已有代码的一个子集,所以我们不一一列出,大家可以在随本文提供的源代码中看到细节。这里只提一下我为Diagram和Shape分别创建了EditPart,名为SimpleDiagramEditPart和SimpleShapeEditPart.
既然我们给自己的Viewer定义了一些EditPart,就必然需要一个EditPart工厂。如下:
清单2. 为我们的Viewer创建EditPart工厂public class SimpleEditPartFactory implements EditPartFactory {   public EditPart createEditPart(EditPart context, Object model) {      EditPart part = null;      if(model instanceof ShapesDiagram)         part = new SimpleDiagramEditPart();      else if(model instanceof Shape)         part = new SimpleShapeEditPart();               if(part != null)         part.setModel(model);               return part;   }}
到这里为止,我们的控制层就算完成了。出于简单的考虑,很多EditPolicy和Command还没有加上,但是它已经有个样子了。其它的功能作为练习留给读者完成。
组装与初始化可是我们的工作并没有做完,Viewer虽然有了,可是显示在哪里呢?我们还需要把它添加到编辑器中,而且这一部分也并非平淡如水,还是有不少工作可以做的。我们先给出完成后的ShapeEditor的createPartControl()代码:
清单3. 组装各个部分public void createPartControl(Composite parent) {   Composite topLevel = new Composite(parent, SWT.NONE);   GridLayout layout = new GridLayout();   layout.marginHeight = 0;   layout.marginWidth = 0;   layout.verticalSpacing = 0;   topLevel.setLayout(layout);      Composite top = new Composite(topLevel, SWT.NONE);   top.setLayout(new FillLayout());   top.setLayoutData(new GridData(GridData.FILL_BOTH));      Composite bottom = new Composite(topLevel, SWT.BORDER);   bottom.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));   bottom.setLayout(new FillLayout());      super.createPartControl(top);      ShapeViewer viewer = new ShapeViewer();   viewer.createControl(bottom);   getEditDomain().addViewer(viewer);   getSelectionSynchronizer().addViewer(viewer);   viewer.setEditPartFactory(new SimpleEditPartFactory());   viewer.setRootEditPart(new ScalableRootEditPart());   viewer.setContents(getModel());}
为了给新的Viewer腾一个地方,我把编辑器划分成上下两个部分,上面的部分仍然是传统的可视化编辑区和调色板, 下面就用来放置新的Viewer。值得注意的是最后五行,首先,最后三行是把我们的模型层,表示层和控制层连接了起来, 这是必须的,就好像机器要运转,零件一个也不能少的道理一样。其次,倒数第四第五行是一些附加工作,没有也没有 关系,不过我们还是应该明白它们是什么意思。getEditDomain().addViewer(viewer)把 我们的Viewer添加到了EditDomain中,这样EditDomain会记住我们的Viewer并且向我们派发一些事件,比如鼠标事件。 而getSelectionSynchronizer().addViewer(viewer)的作用是把我们的Viewer里面的选择 事件和其它Viewer同步起来,这样做的效果是用户在其它Viewer里选择了一个图形之后,我们的Viewer里面也会反映出来, 反之亦然。
还有很多其它的事可以做,比如装载键盘事件处理器,添加拖放支持,等等。所以我把这一部分的工作总结为“组装和初始化”。具体的步骤不一一列举了,留待读者完成。
完成后的Viewer如下图所示:
图2. 自定义Viewer效果图图2中看到,我实现了所有计划的功能,把所有的图形按照创建的顺序排成了一排,并且上下两个Viewer的选择也是同步的。

 

Viewer之间的交互有了多个Viewer之后,一个主要的问题是它们应该如何交互?让Viewer之间互相知道对方并不是一个好的设计,观察GEF中各个Viewer之间的关系,基本上都使用了一些手段来降低它们之间的耦合。比如调色板和编辑区之间通过Request隔离开两个Viewer,标尺和编辑区之间通过Ruler/Guide模型来交互。如果我们需要在我们的Viewer和其它Viewer之间交互,可以参考这样的设计。

 

结束语GEF中的几乎一切东西都可以定制,本文介绍的是Viewer的定制,Viewer是GEF中的顶层结构,其内部结构为MVC模式,可以认为一个Viewer就是一个画板,而我们也可以定制自己的画板。本文提供的代码是一个很基本的例子,还有很多功能留待读者去完成。

 

声明本文仅代表作者的个人观点,不代表IBM的立场。

 

下载描述名字大小Viewer示例代码org.eclipse.gef.examples.shapes_viewer.zip49KB

posted @ 2018-03-22 17:15  懒洋洋躺着的草地  阅读(281)  评论(0编辑  收藏  举报