对于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的创建;分离,指责分离,解耦这恐怕是程序的最高境界了吧,看看高手是怎么做的。唉,我还差的远哪!

你可能感兴趣的:(layout)