对于DirectedGraph以及DirectedGraphLayout的一点思索
凡是研究过GEF的例子Flow的,都应该知道这个例子是可以自动布局的,当向“画图工作区”添加一些图形元素的时候,程序将会自动布局,并以动画的形式表示出来。这个功能看起来很简单,但是实际上却包含了很多需要我们考虑的内容。起初,我对其中的动画部分很感兴趣,非常希望了解更多的实现动画的方式,因为我就是一个俗人,我只想知道怎么才能让程序如此之炫,但是直至深入研究下去才发现,原来还有比这更有意思的东西存在,当然这是以往我所不知道的,这里我把它们写出来,算是一个自我总结吧。
问题出现在查看例子中各个EditPart源码的过程中,我发现在几个EditPart的源码中总是会出现几个方法:
contributeEdgesToGraph(CompoundDirectedGraph graph, Map map);
contributeNodesToGraph(CompoundDirectedGraph graph, Subgraph s, Map map);
applyGraphResults(CompoundDirectedGraph graph, Map map);
applyChildrenResults(CompoundDirectedGraph graph, Map map);
说实话,起初我真的不明白这几个方法究竟是干嘛的,这些方法都是孤立的方法,EditPart中其他方法并没有对它们进行使用,不仅如此,这些方法中还有一个奇怪的参数CompoundDirectedGraph更是让我一头雾水,这个类到底是干什么的呢?最后经过多方查找,并谷歌了半天,才知道它们被用到的底层图形的LayoutManager中。
做GEF的,用脚后跟都知道,“图形工作区”本身是需要一个EditPart的,而这个作为Controller的EditPart是需要一个我称之为底层图形的Figure的,这个Figure是在EditPart的createFigure方法中生成的,在Flow例子中也不例外,通过源码我们可以看出,这个Figure不像我们普通的GEF应用那样使用了XYLayout,而是使用了自定义的GraphLayoutManager。
代码
2 Figure f = new Figure() {
3 public void setBounds(Rectangle rect) {
4 int x = bounds.x, y = bounds.y;
5 boolean resize = (rect.width != bounds.width) || (rect.height != bounds.height), translate = (rect.x != x) || (rect.y != y);
6
7 if (isVisible() && (resize || translate))
8 erase();
9 if (translate) {
10 int dx = rect.x - x;
11 int dy = rect.y - y;
12 primTranslate(dx, dy);
13 }
14 bounds.width = rect.width;
15 bounds.height = rect.height;
16 if (resize || translate) {
17 fireFigureMoved();
18 repaint();
19 }
20 }
21 };
22 f.setLayoutManager(new GraphLayoutManager(this));
23
24 return f;
25 }
26
请注意第22行,由此可见底层图形的布局管理就着落在这个自定义的LayoutManager上了。 那么既然是一个自定义的LayoutManager,那么就让我们见识见识它的庐山真面目吧:
private ActivityDiagramPart diagram;
GraphLayoutManager(ActivityDiagramPart diagram) {
this.diagram = diagram;
}
/**
* 这里的这个container就是安装了这个布局管理器的那个EditPart所对应的Figure
* 在这里,它就是diagram这个EditPart对应的底层图形元素
*/
protected Dimension calculatePreferredSize(IFigure container, int wHint, int hHint) {
container.validate();
List children = container.getChildren();
Rectangle result = new Rectangle().setLocation(container.getClientArea().getLocation());
for (int i = 0; i < children.size(); i++)
result.union(((IFigure) children.get(i)).getBounds());
result.resize(container.getInsets().getWidth(), container.getInsets()
.getHeight());
return result.getSize();
}
public void layout(IFigure container) {
GraphAnimation.recordInitialState(container);
if (GraphAnimation.playbackState(container))
return;
CompoundDirectedGraph graph = new CompoundDirectedGraph();
Map partsToNodes = new HashMap();
diagram.contributeNodesToGraph(graph, null, partsToNodes);
diagram.contributeEdgesToGraph(graph, partsToNodes);
new CompoundDirectedGraphLayout().visit(graph);
diagram.applyGraphResults(graph, partsToNodes);
}
}
呀,怎么这么简单。呵呵,不然。首先,我们看到了上面我们说过的两个不知所谓的方法,由此证明这些方法是和布局有关的,但是我们还发现,这里面还有一个方法new CompoundDirectedGraphLayout().visit(graph);这又是何方神圣?为啥弄个visit的方法名,莫非...
当然,答案是肯定的,Flow例子在布局管理器上使用了访问者模式,我跪倒在地了,我还能说什么,世界上就是有一些人生下来就比我强,不知道各位如何,也许你们会对此嗤之以鼻,会说访问者模式谁不会用,还用说么?但是我不会这样,我只有佩服,Eclipse真的是一座宝库,里面需要我们理解和学习的东西太多了。那么我们就看看CompoundDirectedGraphLayout的庐山真面目吧:
2
3 void init() {
4 steps.add(new TransposeMetrics());
5 steps.add(new CompoundBreakCycles());
6 steps.add(new RouteEdges());
7 steps.add(new ConvertCompoundGraph());
8 steps.add(new InitialRankSolver());
9 steps.add(new TightSpanningTreeSolver());
10 steps.add(new RankAssignmentSolver());
11 steps.add(new CompoundPopulateRanks());
12 steps.add(new CompoundVerticalPlacement());
13 steps.add(new MinCross(new CompoundRankSorter()));
14 steps.add(new SortSubgraphs());
15 steps.add(new CompoundHorizontalPlacement());
16 }
17
18 }
19
这里面steps增加的每一个元素都是一个visitor,每一个visitor都是GraphVisitor的一个子类,它们工作总的来说就是对给定的DirectedGraph进行修改,从上面给出的那么多visitor中,从字面上可以看出有“对边进行路由”,“减少边的交叉”,“垂直摆放”,“水平摆放”等等。当然还有好多不能从字面上翻译,但是至少我们能够看出,布局管理器首先将底层图形及其包含的所有子图形转化为一个DirectedGraph,然后通过访问者模式将相应的DirectedGraph进行了修改,然后再把修改后的结果“布局出来”,布局的过程是通过动画的形式来实现的。套用平原枪声的一句老话:高,实在是高!
到此扯了这么多,那到底啥是DirectedGraph呢?字面上,当然是“有向图”,但是如果这么理解可能会更好,那就是:DirectedGraph是一个抽象概念的图形,它和Figure不同之处在于,它仅仅维护了概念图形中包含了那些节点、这些节点之间有哪些边(Edge),就像我们学习数据结构时讨论的Graph一样,它并不关心这些图形应该如何绘制,你愿意把它画到墙上还是画在纸上,以及你怎么画这个图,它完全不管。为了让它能够正常工作必须将它和相应的EditPart -- Viewer结合起来,由DirectedGraph负责概念领域的图形,对应的Figure负责显示,而EditPart负责Viewer的创建;分离,指责分离,解耦这恐怕是程序的最高境界了吧,看看高手是怎么做的。唉,我还差的远哪!
posted on 2010-08-10 17:41 wayne.wang 阅读(964) 评论(0) 编辑 收藏 举报