EditPart是整个gef的核心层,如果想更好的扩展它,必须了解它的生命周期。
1.物理结构:
整个编辑器从物理结构上来说,其实就是多个EditPartViewer组成,而从展现上来说EditPartViewer提供给编辑器的是一个SWT控件,而不是Figure(Figure也是由SWT发展而来)。对于不同的EditPartViewer会由不同的选中,其中GraphicalViewerImpl的选择就是所有的子组件必须是Figure。
public Control createControl(Composite composite) {
setControl(new Canvas(composite, SWT.NO_BACKGROUND));
return getControl();
}
大家应该清楚Canvas与LightweightSystem的渊源。
我们只关心GraphicalViewerImpl的话会发现,其实从物理结构上来说。整个编辑器的展现层都是由一个GraphicalViewerImpl,和它的若干个EditPart来展现的,而这若干EditPart都是装在一个ScalableRootEditPart里面的,EditPart本身不是展现,但是它的Figure展现。
在GraphicalViewerImpl中会new一个LightweightSystem对象,然后在看看下面这段代码就清楚了。
public void setRootEditPart(RootEditPart editpart) {
super.setRootEditPart(editpart);
setRootFigure(((GraphicalEditPart) editpart).getFigure());
}
protected void setRootFigure(IFigure figure) {
rootFigure = figure;
getLightweightSystem().setContents(rootFigure);
}
ScalableRootEditPart会把自己的Figure放到LightweightSystem里面进行管理。这也是很多书上都会说,我们自己创建的rootFigure不是真正的root。至于LightweightSystem的内部如何管理Figure不在本文详述。除了创建以外,销毁也是这GraphicalViewerImpl里面实现的。
2.操作:
除了生命周期以外,GraphicalViewerImpl里面还有很多对EditPart的操作。常用的像,获取鼠标选中的EditPart,获取光标所在的EditPart,以及选中一个EditPart。凡是对EditPart操作比较粗粒度的处理,都是在GraphicalViewerImpl里面进行处理的。
这个也扯出一个问题,就如同我之前碰到的一个问题,如何选中一个EditPart在编辑器里面。最开始的时候,我是直接用EditPart调用自己的setSelected(int value);。但是事实上,这个只是一种选中的状态,而不是真正的选中了。仔细想想也是,自己能选中自己吗?自己应该只能标记自己的选中状态才对,选中的操作应该由别人发起。事实上,通过GraphicalViewerImpl选中EditPart,属性视图和大纲都会同步。
3.EditPart的内部周期:侧重对IFigure 的描述
上述的一些,只能很浅显的描述一下GraphicalViewerImpl和EditPart的关系,很多时候只知道这些是不够的。在前面的文章,由描述整个gef的事件触发过程,这些事件触发后最终会归结到模型和界面的改变。模型的改变挺容易理解,这里讲一下界面的改变。
1.ScalableRootEditPart:rootPart
首先需要搞清楚ScalableRootEditPart,因为根part相对来说比较特殊。
The layer structure (top-to-bottom) for this root is: Root Layered Pane
├ Guide Layer
├ Feedback Layer
├ Handle Layer
└ Scalable Layers (ScalableLayeredPane)
├ Scaled Feedback Layer
├ Printable Layers
├ Connection Layer
└ Primary Layer
└ Grid Layer
从注释上看,可以知道整个编辑器的最底层,是建立了多个Layer的,每个Layer都有其特定的作用。Primary Layer是我们的组件实体显示的位置。GraphicalEditPart的getFigure();就显示在这层。每个层具体是干嘛的,自己研究。
protected IFigure createFigure() {
Viewport viewport = createViewport();
innerLayers = new LayeredPane();
createLayers(innerLayers);
viewport.setContents(innerLayers);
return viewport;
}
知道这个Viewport是干嘛的吗?这个是有一个概念的,视图和视角,整个编辑器是可以无限大的,而我们看到的部分却是有限的,我们看到的就是 Viewport,Viewport还跟侧滑条的实现相关。
protected ScalableLayeredPane createScaledLayers() {
ScalableLayeredPane layers = new ScalableLayeredPane();
layers.add(createGridLayer(), GRID_LAYER);
layers.add(getPrintableLayers(), PRINTABLE_LAYERS);
layers.add(new FeedbackLayer(), SCALED_FEEDBACK_LAYER);
return layers;
}
滚动层的创建。
了解ScalableRootEditPart最大的意义就是,你知道了你的Figure具体展现在那一层,那个位置。
2.GraphicalEditPart:
我们关注的是图形化界面,所以需要重点关注一下这个接口。其中有三个方法是需要重点关注的:
IFigure getFigure();
IFigure getContentPane();
void setLayoutConstraint(EditPart child, IFigure figure, Object constraint);
getFigure与getContentPane的区别:getFigure是EditPart界面的root,是EditPart向上提供的Figure。ContentPane,是一个容器,用来装EditPart孩子的Figure的容器,默认情况下这两个是同一个。
public void setLayoutConstraint(EditPart child, IFigure childFigure,
Object constraint) {
childFigure.getParent().setConstraint(childFigure, constraint);
}
这个是setLayoutConstraint的默认实现,也就是说在调用这个方法之前,childFigure是肯定有父的。
public void setConstraint(IFigure child, Object constraint) {
if (child.getParent() != this)
throw new IllegalArgumentException("Figure must be a child"); //$NON-NLS-1$
if (layoutManager != null)
layoutManager.setConstraint(child, constraint);
revalidate();
}
其实最终也就是把EditPart里面子的约束,传递给父的Figure的Layout里面了,这样也是为啥第二个参数是Object的原因,因为不同的 Layout,需要的值不一样。
3.AbstractGraphicalEditPart:
protected void addChildVisual(EditPart childEditPart, int index) {
IFigure child = ((GraphicalEditPart) childEditPart).getFigure();
getContentPane().add(child, index);
}
添加子的界面,这个是在添加子EditPart调用的,默认实现如上。
protected IFigure getLayer(Object layer) {
LayerManager manager = (LayerManager) getViewer().getEditPartRegistry()
.get(LayerManager.ID);
return manager.getLayer(layer);
}
获取layer的方法,传递的ID,到Viewer里面去找。
public void refresh() {
refreshVisuals();
refreshChildren();
refreshSourceConnections();
refreshTargetConnections();
}
刷新界面的时候的刷新顺序,如上。
4.内部周期(Add过程):
(1)编辑器会通过setinput获取到文件,然后加载为模型。而GraphicalViewerImpl在构造的时候已经构造了一个ScalableRootEditPart。
(2)获取到文件后,会加载以下内容,其中getGraphicalViewer()就是GraphicalViewerImpl对象,而他的setContents(roots);会获取到模型的root。
@Override
protected void configureGraphicalViewer() {
super.configureGraphicalViewer();
RootEditPart rootEditPart = new ScalableFreeformRootEditPart();
getGraphicalViewer().setRootEditPart(rootEditPart);
getGraphicalViewer().setEditPartFactory(new ContextEditPartFactory());
getGraphicalViewer().setContents(roots);
getGraphicalViewer().setContextMenu(createContextMenuProvider());
getGraphicalViewer().setKeyHandler(getKeyHandler());
}
(3)在setContents内部其实就是调用EditPartFactory创建的EditPart,然后添加到RootEditPart。
public void setContents(Object contents) {
Assert.isTrue(getEditPartFactory() != null,
"An EditPartFactory is required to call setContents(Object)");//$NON-NLS-1$
setContents(getEditPartFactory().createEditPart(null, contents));
}
public void setContents(EditPart editpart) {
getRootEditPart().setContents(editpart);
}
(4)为了关注RootEditPart是如何添加的,我们看看SimpleRootEditPart里面的实现:
public void setContents(EditPart editpart) {
if (contents == editpart)
return;
if (contents != null)
removeChild(contents);
contents = editpart;
if (contents != null)
addChild(contents, 0);
}
它会把editpart添加为子:
protected void addChild(EditPart child, int index) {
Assert.isNotNull(child);
if (index == -1)
index = getChildren().size();
if (children == null)
children = new ArrayList(2);
children.add(index, child);
child.setParent(this);
addChildVisual(child, index);
child.addNotify();
if (isActive())
child.activate();
fireChildAdded(child, index);
}
这个过程中,它会把child的界面元素添加到自己的界面元素里面去addChildVisual(child, index);
protected void addChildVisual(EditPart childEditPart, int index) {
IFigure child = ((GraphicalEditPart) childEditPart).getFigure();
getContentPane().add(child, index);
}
这个就是我们刚才说的默认实现。子添加子的子也是这个顺序。整个过程怎么没使用setLayoutConstraint方法呢?是因为这个方法跟添加无关,跟刷新相关。
5.内部周期(刷新):
(1)首先我们得明白,刷新的源头在那,也就是刷新的这个请求是谁发起的?是模型,模型改变了,是我们自己用代码触发的刷新。我们一般的做法是在自己的EditPart类实现PropertyChangeListener,当属性改变时,我们刷新界面。
@Override
public void propertyChanged(ModelElement element, String propertyName,
Object oldValue, Object newValue)
{
refresh();
}
假设全部刷新,我们会调用refresh方法。
(2)刷新过程:
public void refresh() {
refreshVisuals();
refreshChildren();
refreshSourceConnections();
refreshTargetConnections();
}
refreshVisuals这个刷新自己,也就是自己创建的IFigure,refreshChildren刷新子,refreshSourceConnections和refreshTargetConnections是刷新线的,这里只关注refreshVisuals和refreshChildren。refreshVisuals的默认实现未空,需要自己进行扩展,没啥好说的就是重新画一遍。
@Override
protected void refreshVisuals()
{
((AbstractGraphicalEditPart) getParent()).setLayoutConstraint(this,
getFigure(), model.getBounds());
super.refreshVisuals();
}
上述是比较常见的处理方式,意思就是把自己再塞到父里面去一次。
protected void refreshChildren() {
int i;
EditPart editPart;
Object model;
List children = getChildren();
int size = children.size();
Map modelToEditPart = Collections.EMPTY_MAP;
if (size > 0) {
modelToEditPart = new HashMap(size);
for (i = 0; i < size; i++) {
editPart = (EditPart) children.get(i);
modelToEditPart.put(editPart.getModel(), editPart);
}
}
List modelObjects = getModelChildren();
for (i = 0; i < modelObjects.size(); i++) {
model = modelObjects.get(i);
// Do a quick check to see if editPart[i] == model[i]
if (i < children.size()
&& ((EditPart) children.get(i)).getModel() == model)
continue;
// Look to see if the EditPart is already around but in the
// wrong location
editPart = (EditPart) modelToEditPart.get(model);
if (editPart != null)
reorderChild(editPart, i);
else {
// An EditPart for this model doesn't exist yet. Create and
// insert one.
editPart = createChild(model);
addChild(editPart, i);
}
}
// remove the remaining EditParts
size = children.size();
if (i < size) {
List trash = new ArrayList(size - i);
for (; i < size; i++)
trash.add(children.get(i));
for (i = 0; i < trash.size(); i++) {
EditPart ep = (EditPart) trash.get(i);
removeChild(ep);
}
}
}
上述是刷新子的默认实现,大致就是变量一下,比较关键的一个句代码reorderChild(editPart, i);
protected void reorderChild(EditPart child, int index) {
// Save the constraint of the child so that it does not
// get lost during the remove and re-add.
IFigure childFigure = ((GraphicalEditPart) child).getFigure();
LayoutManager layout = getContentPane().getLayoutManager();
Object constraint = null;
if (layout != null)
constraint = layout.getConstraint(childFigure);
removeChildVisual(editpart);
List children = getChildren();
children.remove(editpart);
children.add(index, editpart);
addChildVisual(editpart, index);
setLayoutConstraint(child, childFigure, constraint);
}
先删除,再添加,然后调用一下setLayoutConstraint进行刷新。关于setLayoutConstraint里面的流程前面已经说了,最终是layout添加了一遍 child。