最近由于實(shí)驗(yàn)室任務(wù)繁重,一直沒(méi)有繼續(xù)研究GEF,本來(lái)已經(jīng)掌握的一些東西好象又丟掉了不少,真是無(wú)奈啊,看來(lái)還是要經(jīng)常碰碰。剛剛接觸GEF的朋友大都會(huì)有這樣的印象:GEF里概念太多,比較繞,一些能直接實(shí)現(xiàn)的功能非要拐幾個(gè)彎到另一個(gè)類里做,而且很多類的名字十分相似,加上不知道他們的作用,感覺(jué)就好象一團(tuán)亂麻。我覺(jué)得這種情況是由圖形用戶界面(GUI)的復(fù)雜性所決定的,GUI看似簡(jiǎn)單,實(shí)際上包含了相當(dāng)多的邏輯,特別是GEF處理的這種圖形編輯方式,可以說(shuō)是最復(fù)雜的一種。GEF里每一個(gè)類,應(yīng)該說(shuō)都有它存在的理由,我們要盡可能了解作者的意圖,這就需要多看文檔和好的例子。
在Eclipse里查看文檔和代碼相當(dāng)便利,比如我們對(duì)某個(gè)類的用法不清楚,一般首先找它的注釋(選中類或方法按F2),其次可以查看它在其他地方用法(選中類或方法按Ctrl+Shift+G),還可以找它的源代碼(Ctrl+鼠標(biāo)左鍵或F3)來(lái)看,另外Ctrl+Shift+T可以按名稱查找一個(gè)類等等。學(xué)GEF是少不了看代碼的,當(dāng)然還需要時(shí)間和耐心。
好,閑話少說(shuō),下面進(jìn)入正題。這篇帖子將繼續(xù)上一篇內(nèi)容,主要討論如何實(shí)現(xiàn)DirectEdit、屬性頁(yè)和大綱視圖,這些都是一個(gè)完整GEF應(yīng)用程序需要提供的基本功能。
實(shí)現(xiàn)DirectEdit
所謂DirectEdit(也稱In-Place-Edit),就是允許用戶在原本顯示內(nèi)容的地方直接對(duì)內(nèi)容進(jìn)行修改,例如在Windows資源管理器里選中一個(gè)文件,然后按F2鍵就可以開(kāi)始修改文件名。實(shí)現(xiàn)DirectEdit的原理很直接:當(dāng)用戶發(fā)出修改請(qǐng)求(REQ_DIRECT_EDIT)時(shí),就在文字內(nèi)容所在位置覆蓋一個(gè)文本框(也可以是下拉框,這里我們只討論文本的情況)作為編輯器,編輯結(jié)束后,再將編輯器中的內(nèi)容應(yīng)用到模型里即可。(作為類似的功能請(qǐng)參考:給表格的單元格增加編輯功能)

圖1 Direct Edit
在GEF里,這個(gè)彈出的編輯器由DirectEditManager類負(fù)責(zé)管理,在我們的NodePart類里,通過(guò)覆蓋performRequest()方法響應(yīng)用戶的DirectEdit請(qǐng)求,在這個(gè)方法里一般要構(gòu)造一個(gè)DirectEditManager類的實(shí)例(例子中的NodeDirectEditManager),并傳入必要的參數(shù),包括接受請(qǐng)求的EditPart(就是自己,this)、編輯器類型(使用TextCellEditor)以及用來(lái)定位編輯器的CellEditorLocator(NodeCellEditorLocator),然后用show()方法使編輯器顯示出來(lái),而編輯器中顯示的內(nèi)容已經(jīng)在構(gòu)造方法里得到。簡(jiǎn)單看一下NodeCellEditorLocator類,它的關(guān)鍵方法在relocate()里,當(dāng)編輯器里的內(nèi)容改變時(shí),這個(gè)方法被調(diào)用從而讓編輯器始終處于正確的坐標(biāo)位置。DirectEditManager有一個(gè)重要的initCellEditor()方法,它的主要作用是設(shè)置編輯器的初始值。在我們的例子里,初始值設(shè)置為被編輯NodePart對(duì)應(yīng)模型 (Node)的name屬性值;這里還另外完成了設(shè)置編輯器字體和選中全部文字(selectAll)的功能,因?yàn)檫@樣更符合一般使用習(xí)慣。
在NodePart里還要增加一個(gè)角色為DIRECT_EDIT_ROLE的EditPolicy,它應(yīng)該繼承自DirectEditPolicy,有兩個(gè)方法需要實(shí)現(xiàn):getDirectEditCommand()和showCurrentEditValue(),雖然還未遇到過(guò),但前者的作用你不應(yīng)該感到陌生--在編輯結(jié)束時(shí)生成一個(gè)Command對(duì)象將修改結(jié)果作用到模型;后者的目的是更新Figure中的顯示,雖然我們的編輯器覆蓋了Figure中的文本,似乎并不需要管Figure的顯示,但在編輯中時(shí)刻保持這兩個(gè)文本的一致才不會(huì)出現(xiàn)"蓋不住"的情況,例如當(dāng)編輯器里的文本較短時(shí)。
實(shí)現(xiàn)屬性頁(yè)
在GEF里實(shí)現(xiàn)屬性頁(yè)和普通應(yīng)用程序基本一樣,例如我們希望可以通過(guò)屬性視圖(PropertyView)顯示和編輯每個(gè)節(jié)點(diǎn)的屬性,則可以讓Node類實(shí)現(xiàn)IPropertySource接口,并通過(guò)一個(gè)IPropertyDescriptor[]類型的成員變量描述要在屬性視圖里顯示的那些屬性。有朋友問(wèn),要在屬性頁(yè)里增加一個(gè)屬性都該改哪些地方,主要是三個(gè)地方:首先要在你的IPropertyDescriptor[]變量里增加對(duì)應(yīng)的描述,包括屬性名和屬性編輯方式(比如文本或是下拉框,如果是后者還要指定選項(xiàng)列表),其次是getPropertyValue()和setPropertyValue()里增加讀取屬性值和將結(jié)果寫(xiě)入的代碼,這兩個(gè)方法里一般都是像下面的結(jié)構(gòu)(以前者為例):
public Object getPropertyValue(Object id) {
if (PROP_NAME.equals(id))
return getName();
if (PROP_VISIBLE.equals(id))
return isVisible() ? new Integer(0) : new Integer(1);
return null;
}
也就是根據(jù)要處理的屬性名做不同操作。要注意的是,下拉框類型的編輯器是以Integer類型數(shù)據(jù)代表選中項(xiàng)序號(hào)的,而不是int或String,例如上面的代碼根據(jù)visible屬性返回第零項(xiàng)或第一項(xiàng),否則會(huì)出現(xiàn)ClassCastException。

圖2 屬性頁(yè)
實(shí)現(xiàn)大綱視圖
在Eclipse里,當(dāng)編輯器(Editor)被激活時(shí),大綱視圖自動(dòng)通過(guò)這個(gè)編輯器的getAdapter()方法尋找它提供的大綱(大綱實(shí)現(xiàn)IcontentOutlinePage接口)。GEF提供了ContentOutlinePage類用來(lái)實(shí)現(xiàn)大綱視圖,我們要做的就是實(shí)現(xiàn)一個(gè)它的子類,并重點(diǎn)實(shí)現(xiàn)createControl()方法。ContentOutlinePage是org.eclipse.ui.part.Page的一個(gè)子類,大綱視圖則是PageBookView的子類,在大綱視圖中有一個(gè)PageBook,包含了很多Page并可以在它們之間切換,切換的依據(jù)就是當(dāng)前活動(dòng)的Editor。因此,我們?cè)赾reateControl()方法里要做的就是構(gòu)造這個(gè)Page,簡(jiǎn)化后的代碼如下所示:
private Control outline;
public OutlinePage() {
super(new TreeViewer());
}
public void createControl(Composite parent) {
outline = getViewer().createControl(parent);
getSelectionSynchronizer().addViewer(getViewer());
getViewer().setEditDomain(getEditDomain());
getViewer().setEditPartFactory(new TreePartFactory());
getViewer().setContents(getDiagram());
}
由于我們?cè)跇?gòu)造方法里指定了使用樹(shù)結(jié)構(gòu)顯示大綱,所以createControl()里的第一句就會(huì)使outline變量得到一個(gè)Tree(見(jiàn)org.eclipse.gef.ui.parts.TreeViewer的代碼),第二句把TreeViewer加到選擇同步器中,從而讓用戶不論在大綱或編輯區(qū)域里選擇EditPart時(shí),另一方都能自動(dòng)做出同樣的選擇;最后三行的作用在以前的帖子里都有介紹,總體目的是把大綱視圖的模型與編輯區(qū)域的模型聯(lián)系在一起,這樣,對(duì)于同一個(gè)模型我們就有了兩個(gè)視圖,體會(huì)到MVC的好處了吧。
實(shí)現(xiàn)大綱視圖最重要的工作基本就是這些,但還沒(méi)有完,我們要在init()方法里綁定UNDO/REDO/DELETE等命令到Eclipse主窗口,否則當(dāng)大綱視圖處于活動(dòng)狀態(tài)時(shí),主工具條上的這些命令就會(huì)變?yōu)椴豢捎脿顟B(tài);在 getControl()方法里要返回我們的outline成員變量,也就是指定讓這個(gè)控件出現(xiàn)在大綱視圖中;在dispose()方法里應(yīng)該把這個(gè)TreeViewer從選擇同步器中移除;最后,必須在PracticeEditor里覆蓋getAdapter()方法,前面說(shuō)過(guò),這個(gè)方法是在Editor激活時(shí)被大綱視圖調(diào)用的,所以在這里必須把我們實(shí)現(xiàn)好的OutlinePage返回給大綱視圖使用,代碼如下:
public Object getAdapter(Class type) {
if (type == IContentOutlinePage.class)
return new OutlinePage();
return super.getAdapter(type);
}
這樣,樹(shù)型大綱視圖就完成了,見(jiàn)下圖。很多GEF應(yīng)用程序同時(shí)具有樹(shù)型和縮略圖兩種大綱,實(shí)現(xiàn)的基本思路是一樣的,但代碼會(huì)稍微復(fù)雜一些,因?yàn)檫@兩種大綱一般要通過(guò)一個(gè)PageBook進(jìn)行切換,縮略圖一般由org.eclipse.draw2d.parts.ScrollableThumbnail負(fù)責(zé)實(shí)現(xiàn),這里暫時(shí)不講了(也許以后會(huì)詳細(xì)說(shuō)),你也可以通過(guò)看logic例子的LogicEditor這個(gè)類的代碼來(lái)了解。

圖3 大綱視圖
P.S.寫(xiě)這篇帖子的時(shí)候,我對(duì)例子又做了一些修改,都是和這篇帖子所說(shuō)的內(nèi)容相關(guān)的,所以如果你以前下載過(guò),會(huì)發(fā)現(xiàn)那時(shí)的代碼與現(xiàn)在稍有不同(功能還是完全一樣的,下載)。另外要說(shuō)一下,這個(gè)例子并不完善,比如刪除一個(gè)節(jié)點(diǎn)的時(shí)候,它的連接就沒(méi)同時(shí)刪除,一些鍵盤(pán)快捷鍵不起作用,還存在很多被注釋掉的代碼等等。如果有興趣你可以來(lái)修改它們,也是不錯(cuò)的學(xué)習(xí)途徑。