凡是研究過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更是讓我一頭霧水,這個類到底是幹什麼的呢?最後經過多方尋找,並Google了半天,才知道它們被用到的底層圖形的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的建立;分離,指責分離,解耦這恐怕是程式的最高境界了吧,看看高手是怎麼做的。唉,我還差的遠哪!