对于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。
代码

 1 protected IFigure createFigure() {
 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,那么就让我们见识见识它的庐山真面目吧:

 

代码
class GraphLayoutManager extends AbstractLayout {

    
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的庐山真面目吧:

代码
 1 public final class CompoundDirectedGraphLayout extends DirectedGraphLayout {
 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编辑  收藏  举报

导航