工作以来一直都在做IDE,写篇文章记录下自己对gef的理解,再粗糙也是自己的理解,呵呵!
gef主要是用于实现图形化编辑器的一个框架,尽管它主要用于实现编辑器,但是它还是可以做其它的东西的,例如官方提供的tree节点的大纲视图,就是建立在gef框架基础之上的。
1.Tool
EditDomain:
对于一个gef应用来说,EditDomain对象是全局唯一的,对于一个gef应用来说,EditDomain基本上是顶层控制器。它管理者EditPartViewer,PaletteViewer,CommandStack,activeTool。这里觉得奇怪的是为啥要有PaletteViewer,PaletteViewer至少应该在子类里面存在才是合适的吧,个人觉得PaletteViewer并不是一个gef应用必须的。
在EditDomain里面,EditDomain会转发来自EditPartViewer,PaletteViewer的请求,把它们转发给当前的activeTool,让activeTool处理这些事件。CommandStack命令栈,这个就不说了,不知道的别玩了,洗洗睡算了。
EditDomain自己是不构建Tool的,它都是从响应的Viewer里面获取的,也就是说Viewer提供了事件以为,还提供了处理事件的tool。
Tool:
想理解Tool是干嘛的,Tool接口上有一段详细的英文说明。Tool具备以下功能:
(1)获取EditDomain和EditPartViewer的事件,并把这些事件转换成请求,让相应的EditPart进行响应和处理。
(2)它自己本身也可以处理一些事件,例如切换选中状态,让viewer滚动,执行一个命令之类的。
说白了,Tool就是对界面事件的第一道处理工序,至于是自己直接处理,还是转发给EditPart,完全在于此事件的复杂度。
注意:官方说明,不要直接实现Tool接口,而是继承其AbstractTool,这是个建议。
Tool的继承关系,可以直接通过F4查看,整个结构比较简洁清晰,基本上gef所有的行为都能找到对应的Tool。eclipse最大的特点就是见文识意,基本上当你需要的时候,直接看实现类的名字就能找到地方了。具体的我自己也没研究。
常见Tool:
DragTracker:一个继承自Tool的接口,主要是用于响应拖拽操作的。
MarqueeSelectionTool:MarqueeToolEntry用的,选中编辑器里面的多个组件
ConnectionBendpointTracker:移动或创建连线上面的拐点的Tool
ResizeTracker:改变组件size的tool
RulerDragTracker:移动游标的,这个跟编辑器的Ruler效果相关
TargetingTool:是所有需要把事件请求转发给Editpart执行的tool的基类。
ConnectionCreationTool:用于连线的tool,ConnectionDragCreationTool与其功能相仿
SelectEditPartTracker:操作EditPart的,只是响应选中,编辑,打开等操作,拖拽由其子类实现。
DragEditPartsTracker:在父的基础上实现了移动的能力
DragTreeItemsTracker:拖拽树节点,大纲视图用到
SelectionTool:这个tool是很关键的一个tool,它是EditDomain默认提供的一个tool,我们说过EditDomain会把事件给相应的tool进行处理。但是事实上PaletteViewer获取当前活动的tool是有一个方法的:paletteViewer.getActiveTool(),但是EditPartViewer却没有这样的方法,而且EditDomain也没有什么调用特殊的方法获取EditPartViewer的tool。最后发现其实SelectionTool这个默认的tool会到EditPart里面获取相应的tool。
2.PaletteViewer
PaletteViewer:
在编辑器里面除了EditPartViewer以外还有PaletteViewer。PaletteViewer就是我们看到的面板,它也是用gef实现的。在PaletteViewer中有一套数据模型,其根类是PaletteEntry,它相当于gef里面的模型层,记录这界面的显示信息,以及实体的操作信息。
PaletteEntry的子类:
PaletteContainer:所有继承此类的类,都表示的是palette上的容器节点,例如 PaletteDrawer 就是我们常用的可折叠容器,PaletteRoot代表着palette的根节点,也是继承这个类。
ToolEntry:这个类的含义是,所有继承了它的类,都能够在编辑器中使用。例如gef默认提供的SelectionToolEntry:选中节点的那个tool,MarqueeToolEntry:画一个矩形框选中多个组件的tool,CreationToolEntry:以及我们创建一个界面元素常用的tool
其他:PaletteSeparator:分隔符,PaletteTemplateEntry:模板(这个没用过)
刚才提到ToolEntry可以在编辑器使用,也就是说它能够在编辑器上做一些行为,例如拖拽创建,点击选中,拖拽选中之类的操作。之所以能有这些能力,主要是因为在构造一个ToolEntry的时候同事会传一个Tool类型进去,之后使用的时候会创建一个Tool。
CreationToolEntry构造方法里面的代码:它会传一个CreationTool类型的tool进去,它会把基本的拖拽事件转换为创建请求,传递给EditPart处理
super(label, shortDesc, iconSmall, iconLarge, CreationTool.class);
SelectionToolEntry的构造方法里面的代码类似,不同的是它给的是一个SelectionTool用于选中的。
super(label, shortDesc, SharedImages.DESC_SELECTION_TOOL_16, SharedImages.DESC_SELECTION_TOOL_24, SelectionTool.class);
3.EditPartViewer
ISelectionProvider:
ISelectionProvider是一个jface的接口,EditPartViewer继承于ISelectionProvider,ISelectionProvider主要用于管理选中节点的,这套机制并不属于gef或者编辑器的,而是eclipse的内部机制,也可以说是SWT的选中节点的管理机制。
EditPartViewer:
EditPartViewer继承了ISelectionProvider,标志着所有的EditPartViewer都可以作为一个ISelection源在eclipse内部进行流转。一个gef架构的图形化编辑器是,建立在一套EditPartViewer的基础之上的,用我的理解称EditPartViewer为一个业务单元。EditPartViewer接口里面的方法很多,大致分为几个方向:操作(菜单,快捷键,光标之类),事件(拖拽之类的),对EditPart的管理(EditDomain,EditPartFactory,提供根据鼠标位置获得EditPart的接口,flush),createControl方法提供对原始的SWT组件界面的支持(验证了在大纲视图中使用的能力)。
可以说EditPartViewer就像gef的脸,它是直接面对用户的,用户所有的操作和请求,都是从它这里进行转发的。
EditPartViewer接口也是不建议自己直接实现的,而是继承此类AbstractEditPartViewer,在AbstractEditPartViewer里面有一个SelectionManager对象,用于管理当前Viewer的选中模型,在SelectionManager里面有一个appendSelection(EditPart editpart)方法,从参数名就可以看出。我们在Viewer上选中的是一个个EditPart 对象,这也是为啥,我们在属性视图会获取到EditPart 而不是Viewer的原因。
4.上述三者之间的联系
上述三者肯定是由EditDomain组织起来的,而最终的结果肯定是:Viewer监听事件,并响应事件,把事件传递给EditDomain,EditDomain找到相应的tool来进行处理,而这个tool又是Viewer自己提供的。EditDomain起到了一个中枢的作用。那么整个过程是怎样的呢?
PaletteViewer:
PaletteViewer提供了获取ActiveTool的方法,所以对于PaletteViewer来说,处理方式很简单。
private void handlePaletteToolChanged() { PaletteViewer paletteViewer = getPaletteViewer(); if (paletteViewer != null) { ToolEntry entry = paletteViewer.getActiveTool(); if (entry != null) setActiveTool(entry.createTool()); else setActiveTool(getDefaultTool()); } }
在EditDomain里面有一个handlePaletteToolChanged方法,在选中面板上的节点的时候会进入到这个方法。它会把获取当前的PaletteTool,当面板上的选中失去的时候,又会进入到这个方法。但执行的是setActiveTool(getDefaultTool());
EditPartViewer:
上面也说道了一点,EditPartViewer是不直接提供tool的,在EditDomain里面所有跟EditPartViewer相关的操作都会给SelectionTool(默认提供的tool),然后在SelectionTool里面会找到EditPartViewer相应的EditPart,EditPart会提供可用的ActiveTool。
SelectionTool这整个类就是想办法获取EditPart里面的那个可用的Tool,针对同步的事件类型,选择不同的获取方式,这里贴出其中一个方法,其他的方法与其类似:
protected boolean handleButtonDown(int button) { if (!stateTransition(STATE_INITIAL, STATE_DRAG)) { resetHover(); return true; } resetHover(); EditPartViewer viewer = getCurrentViewer(); Point p = getLocation(); if (getDragTracker() != null) getDragTracker().deactivate(); if (viewer instanceof GraphicalViewer) { Handle handle = ((GraphicalViewer) viewer).findHandleAt(p); if (handle != null) { setDragTracker(handle.getDragTracker()); return true; } } updateTargetRequest(); ((SelectionRequest) getTargetRequest()).setLastButtonPressed(button); updateTargetUnderMouse(); EditPart editpart = getTargetEditPart(); if (editpart != null) { setDragTracker(editpart.getDragTracker(getTargetRequest())); lockTargetEditPart(editpart); return true; } return false; }
其中最关键的一步:setDragTracker,然后其他相应事件的方法就调用这个Tool来对事件进行处理了。
附:
我们在刚入门的时候,通常只会关注如何扩展EditPart,以及它里面的东西,很少关注EditPart由谁管理,如何管理。这篇文章很浅,因为作者很浅,但是希望对不清楚的人有点帮助。