日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

GEF 介紹

 gujin2006 2007-04-14

 

 

簡(jiǎn)介

繪圖編輯框架(GEF)被設(shè)計(jì)用來(lái)以圖形而不是文本的方式來(lái)編輯用戶數(shù)據(jù),一般被稱為模型model)。當(dāng)處理包含多對(duì)多,一對(duì)多以及其他復(fù)雜關(guān)系的實(shí)體時(shí),GEF是一種很有價(jià)值的工具。隨著Eclipse Rich Client Platform 的流行,使得編輯器的開(kāi)發(fā)不僅僅局限于編程,GEF的重要性也與日俱增。比如說(shuō),數(shù)據(jù)庫(kù)schema編輯器 [7],邏輯電路編輯器和任務(wù)流管理器,這些例子都很好地展示了GEF是一種可以用于各個(gè)不同領(lǐng)域的,具有強(qiáng)大功能和靈活性的框架。

然而,任何通用框架都設(shè)計(jì)復(fù)雜,難于學(xué)習(xí),GEF也不例外。到現(xiàn)在為止,最小的例子也將涉及75個(gè)類(lèi)。即使對(duì)于最勤勉的開(kāi)發(fā)者來(lái)說(shuō),要從GEF用戶定義類(lèi)型和GEF提供的上百種類(lèi)型之間相互作用來(lái)理解GEF的獨(dú)特之處,對(duì)耐心和智力的都是一種考驗(yàn)。為了改變這種狀況,一個(gè)全新的,規(guī)模更小的編輯器例子被添加進(jìn)即將到來(lái)的Eclipse 3.1(譯:翻譯此文時(shí),Eclipse 3.1已經(jīng)發(fā)布)。這個(gè)幾何圖形編輯器(看圖1)允許你創(chuàng)建,編輯簡(jiǎn)單的圖。它處理兩種對(duì)象,矩形和橢圓。你可以在實(shí)線和虛線這兩種連接類(lèi)型中選擇一種來(lái)連接兩個(gè)對(duì)象。每一個(gè)連接都是有方向的,也就是說(shuō)從一個(gè)源對(duì)象開(kāi)始,在目標(biāo)對(duì)象處終止。箭頭用來(lái)表示連接方向。連接可以轉(zhuǎn)移,也就是通過(guò)拖動(dòng)它的源點(diǎn)或目標(biāo)點(diǎn)到一個(gè)新的對(duì)象上。編輯器中的對(duì)象可以點(diǎn)擊選中,也可以通過(guò)拖拉一個(gè)區(qū)域來(lái)選擇。選中的對(duì)象可以被刪除。所有的模型操作,比如添加,刪除對(duì)象,移動(dòng)對(duì)象,改變大小等等,都可以u(píng)ndo或redo。最后,編輯器集成了兩個(gè)Eclipse標(biāo)準(zhǔn)視圖PropertiesOutline。這個(gè)編輯器的價(jià)值不是在于它的可用性,而是作為例子,通過(guò)有限的兩種用戶定義類(lèi)型來(lái)演示在一個(gè)成熟GEF編輯器中會(huì)碰到的大多數(shù)概念和技術(shù)。

Screen shot of the diagram editor
圖 1. 運(yùn)行在Linux下的幾何圖形編輯器

Try it!將最新的Eclipse 3.1 GEF例子從GEF項(xiàng)目下載頁(yè)面下載下來(lái),并解壓縮至你的Eclipse目錄中。按Ctrl-N,會(huì)彈出創(chuàng)建向?qū)В瑢?em>Examples目錄展開(kāi),選擇Shapes Diagram。下面給出幾何圖內(nèi)部工作的詳細(xì)的全面介紹。在我們接觸代碼前,我們先來(lái)看看GEF主要思想。

GEF核心概念

GEF幫助你為數(shù)據(jù)構(gòu)造一個(gè)可視化的編輯器。數(shù)據(jù)可以是帶有簡(jiǎn)單溫度旋鈕的溫度調(diào)節(jié)器,也可以是一個(gè)包含幾百個(gè)路由器,連接和服務(wù)質(zhì)量策略的虛擬局域網(wǎng)。幸虧GEF設(shè)計(jì)者,他們?cè)O(shè)法建立一種框架,使得它能夠和任何數(shù)據(jù)一起工作,用GEF的術(shù)語(yǔ)來(lái)說(shuō),就是任何模型(model)。這是通過(guò)嚴(yán)格遵循了模型-視圖-控制器模式(MVC)來(lái)做到的。模型就是你的數(shù)據(jù)。對(duì)于GEF,模式可以是任何普通的Java對(duì)象(POJO)。模型不應(yīng)該知道任何有關(guān)于控制器或視圖的信息。視圖(view)是模型或其某一部分在屏幕上的可視化表示。它可以是矩形,線或橢圓這樣的簡(jiǎn)單圖形,也可以是彼此嵌套的邏輯電路。同時(shí),視圖也應(yīng)該對(duì)模型和控制器一無(wú)所知。雖然任何實(shí)現(xiàn)IFigure接口的類(lèi)都可以作為視圖,但是GEF使用Draw2D可視圖形(figure)。控制器,可稱為編輯部件(edit part),是模型和視圖之間的橋梁。當(dāng)你開(kāi)始編輯你的模型時(shí),一個(gè)頂層的控制器被創(chuàng)建出來(lái)。如果模型由若干個(gè)片段組成,頂層控制器就會(huì)將這個(gè)信息通知GEF。接下來(lái),每個(gè)片段的子控制器被創(chuàng)建出來(lái)。如果它們又包含子片段,這個(gè)過(guò)程就會(huì)一直繼續(xù)下去,直到所有組成模型的對(duì)象都有它們的控制器??刂破鞯牧硪粋€(gè)任務(wù)是創(chuàng)建可視圖形來(lái)表示模型。一旦模型被設(shè)置到某個(gè)控制器,GEF就向控制器要合適的IFigure對(duì)象。既然模型和視圖彼此都不知道對(duì)方,控制器負(fù)責(zé)監(jiān)聽(tīng)模型的修改,并更新模型的可視化表示。結(jié)果,在許多GEF編輯器中,一個(gè)常見(jiàn)的模式就是模型發(fā)PropertyChangeEvent通知。當(dāng)一個(gè)編輯部件收到事件通知時(shí),它通過(guò)調(diào)整模型的外觀或結(jié)構(gòu)上的表示來(lái)作相應(yīng)的改變。

可視編輯的另一個(gè)方面就是對(duì)用戶動(dòng)作和鼠標(biāo),鍵盤(pán)事件作出響應(yīng)。這里的挑戰(zhàn)在于提供一種機(jī)制,提供合理的缺省行為,并且允許重新定義行為來(lái)覆蓋缺省行為,以適應(yīng)所編輯模型。比如鼠標(biāo)拖動(dòng)事件,如果我們假設(shè)每次檢測(cè)到鼠標(biāo)拖動(dòng)事件,所選中對(duì)象都被移動(dòng)的話,我們就限制編輯器開(kāi)發(fā)者的自由。很有可能有人希望在鼠標(biāo)拖動(dòng)的時(shí)候,提供放大,縮小的行為。GEF通過(guò)使用工具(tool),請(qǐng)求(request)和策略(policy)解決了這個(gè)問(wèn)題。

工具是一種有狀態(tài)的對(duì)象,它將象鼠標(biāo)按鈕被按下,被拖動(dòng)等低層事件翻譯成高層的由Request對(duì)象表示的請(qǐng)求。發(fā)送哪個(gè)請(qǐng)求取決于所激活的工具。例如,連接工具在收到鼠標(biāo)按鈕被按下這樣的事件時(shí),會(huì)發(fā)送一個(gè)連接開(kāi)始或結(jié)束的請(qǐng)求。如果是一個(gè)創(chuàng)建工具,我們就會(huì)收到一個(gè)創(chuàng)建請(qǐng)求。GEF包含了大量預(yù)定義的工具以及創(chuàng)建應(yīng)用特定工具的方法。工具可以由程序控制激活,也可以在用戶實(shí)施一個(gè)動(dòng)作后激活。在大多數(shù)情況下,工具將請(qǐng)求發(fā)送給鼠標(biāo)位置下面的圖形的EditPart。例如,如果你點(diǎn)擊一個(gè)代表widget的矩形,與此相關(guān)的編輯部件就會(huì)收到一個(gè)選中請(qǐng)求或者直接編輯的請(qǐng)求。有時(shí)候,請(qǐng)求會(huì)發(fā)送給區(qū)域中的所有可視圖形的編輯部件,比如MarqueeSelectionTool就是這樣。無(wú)論一個(gè)或多個(gè)編輯部件怎樣被選擇為請(qǐng)求目標(biāo),它們自己并不處理請(qǐng)求。而是將這個(gè)任務(wù)交給所注冊(cè)的編輯策略edit policies)。每個(gè)編輯策略都會(huì)為該請(qǐng)求提供一個(gè)命令。不希望處理請(qǐng)求的策略將返回一個(gè)null。使用策略而不是編輯部件來(lái)響應(yīng)請(qǐng)求的機(jī)制使得策略和編輯部件都盡可能短小,功能集中。同時(shí),也意味著調(diào)試和維護(hù)代碼變得更容易。GEF的最后一個(gè)部分就是命令command)。GEF并沒(méi)有直接修改模型,它要求你使用命令來(lái)做實(shí)際的修改。每個(gè)命令應(yīng)該實(shí)現(xiàn)執(zhí)行對(duì)模型或模型一部分的修改和撤銷(xiāo)修改。這樣,GEF編輯器自動(dòng)支持模型修改的undo/redo。

除了能夠提升你的技能以及設(shè)計(jì)模式方面的知識(shí)外,使用GEF的一個(gè)重要的優(yōu)點(diǎn)在于它能夠和Eclipse平臺(tái)完全集成在一起。在編輯器中選中的對(duì)象可以為標(biāo)準(zhǔn)Properties視圖提供屬性。Eclipse向?qū)Э梢杂脕?lái)創(chuàng)建,初始化GEF編輯器編輯的模型。Edit菜單中的UndoRedo可以觸發(fā)GEF編輯修改的撤銷(xiāo)和重做。簡(jiǎn)單地說(shuō),GEF編輯器實(shí)現(xiàn)IEditorPart接口,是Eclipse平臺(tái)中的一員,它和文本編輯器或其他workbench編輯器處于同樣的集成層次。

模型

創(chuàng)建GEF編輯器的第一步是創(chuàng)建模型。在我們的例子里,模型由四類(lèi)對(duì)象組成:幾何圖(包含所有的圖形),兩種類(lèi)型的圖形,和圖形間的連接。在我們?yōu)檫@些類(lèi)編寫(xiě)代碼前,我們準(zhǔn)備了一些基礎(chǔ)結(jié)構(gòu)。

核心模型類(lèi)

當(dāng)你創(chuàng)建模型時(shí),你可以參考下面的內(nèi)容:

  • 模型存儲(chǔ)了所有用戶可以編輯或?yàn)g覽的數(shù)據(jù)。這同時(shí)也包括和可視化表示相關(guān)的數(shù)據(jù),比如邊界。你不能依賴編輯部件或可視圖形來(lái)保存這些數(shù)據(jù),因?yàn)檫@些對(duì)象可能根據(jù)需要?jiǎng)?chuàng)建或丟棄。如果你不喜歡將你的可視數(shù)據(jù)和你的業(yè)務(wù)數(shù)據(jù)綁定在一起,可以參考[3]中的建議。
  • 提供持久化模型的方法。確信當(dāng)編輯器在關(guān)閉時(shí),你的模型被持久化。當(dāng)同樣的編輯器被打開(kāi)時(shí),實(shí)現(xiàn)方法使得模型狀態(tài)可以從持久存儲(chǔ)器中恢復(fù)。
  • 模型必須保持與視圖或控制器無(wú)關(guān)。不要存儲(chǔ)任何對(duì)視圖或控制器的引用。GEF在某種條件下會(huì)丟棄視圖或控制器。如果你保持了這些引用,你可能會(huì)碰到一個(gè)失效的可視圖形或編輯部件。
  • 提供方法允許別人監(jiān)聽(tīng)模型的變化。這使得控制器可以及時(shí)響應(yīng)修改,并對(duì)視圖作適當(dāng)調(diào)整。既然你不能保持對(duì)控制器的引用,唯一的方法就是為控制器提供一種途徑,使得它能夠作為一個(gè)事件接受者注冊(cè)(和撤銷(xiāo)注冊(cè))在模型上。一個(gè)好的辦法就是使用java.beans包中的屬性修改事件通知。

上面所列的規(guī)則對(duì)于所有模型都是相同的,為基本類(lèi)建立類(lèi)層次來(lái)強(qiáng)化這些規(guī)則是很有好處的。ModelElement類(lèi)繼承了Java的Object類(lèi),并提供了三個(gè)功能:持久化,屬性改變和屬性源支持。簡(jiǎn)單的模型持久化可以通過(guò)實(shí)現(xiàn)tagjava.io.Serializable接口以及tagreadObject方法來(lái)完成。這使得你可以將編輯器的模型以二進(jìn)制格式存儲(chǔ)。當(dāng)需要和某種應(yīng)用一起工作時(shí),這并不能提供的格式的可移植性。在復(fù)雜的情況下,你需要實(shí)現(xiàn)將模型以XML或類(lèi)似的格式存儲(chǔ)。模型的改變通過(guò)屬性改變事件來(lái)通知。這個(gè)基本類(lèi)允許編輯部件tag注冊(cè)和tag撤銷(xiāo)注冊(cè)為屬性改變通知的接受者。屬性改變通知是通過(guò)調(diào)用tagfirePropertyChange方法觸發(fā)的。最后,為了幫助和workbench的Properties視圖集成,需要實(shí)現(xiàn)IPropertySource接口(細(xì)節(jié)在圖2中忽略)。

public abstract class ModelElement implements tag IPropertySource, tag Serializable {

private transient PropertyChangeSupport pcsDelegate =
new PropertyChangeSupport(this);

tag public synchronized void addPropertyChangeListener(PropertyChangeListener l) {
if (l == null) {
throw new IllegalArgumentException();
}

pcsDelegate.addPropertyChangeListener(l);
}

tag protected void firePropertyChange(String property,
Object oldValue,
Object newValue) {
if (pcsDelegate.hasListeners(property)) {
pcsDelegate.firePropertyChange(property, oldValue, newValue);
}
}

tag private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
in.defaultReadObject();
pcsDelegate = new PropertyChangeSupport(this);
}

tag public synchronized void removePropertyChangeListener(PropertyChangeListener l) {
if (l != null) {
pcsDelegate.removePropertyChangeListener(l);
}
}

...
}
圖 2. 所有模型對(duì)象的基類(lèi)

橢圓和矩形這兩類(lèi)對(duì)象,在許多方面是相同的,它們的公共功能可以被提取出來(lái)放在公共類(lèi)中。尤其是兩者都代表著占據(jù)某個(gè)位置,具有一定大小的對(duì)象。它們可以彼此連接。這些屬性的任何修改都需要通知監(jiān)聽(tīng)者。更進(jìn)一步地說(shuō),它們的位置和大小屬性都可以通過(guò)IPropertySource接口暴露,這允許用戶通過(guò)Properties視圖來(lái)查看,和修改它們。

對(duì)象間連接的管理很值得仔細(xì)看一下。這里并沒(méi)有一個(gè)全局的用于存儲(chǔ)所有連接的地方。GEF要求模型部件報(bào)告它們之間的連接的情況,是源還是目標(biāo)。這些信息都以List對(duì)象的形式提供。Shape類(lèi)維護(hù)了兩個(gè)數(shù)組列表,分別存儲(chǔ)tag源連接和tag目標(biāo)連接。源連接是指那些以當(dāng)前圖形作為源的連接,目標(biāo)連接是指以當(dāng)前圖形作為目標(biāo)的連接。兩個(gè)包可見(jiàn)方法(tagtag)使得圖形和連接可以彼此知道相互之間的關(guān)系。此外,兩個(gè)公有方法(tagtag)使得model包外面的類(lèi)知道圖形的連接情況。這些方法都會(huì)被相關(guān)的圖形(形狀)控制器所使用,具體內(nèi)容將在接下來(lái)的部分中加以介紹。

public abstract class Shape extends ModelElement {

private Point location = new Point(0, 0);
private Dimension size = new Dimension(50, 50);
tag private List sourceConnections = new ArrayList();
tag private List targetConnections = new ArrayList();

public Point getLocation() {
return location.getCopy();
}

public void setLocation(Point newLocation) {
if (newLocation == null) {
throw new IllegalArgumentException();
}
location.setLocation(newLocation);
firePropertyChange(LOCATION_PROP, null, location);
}

tag void addConnection(Connection conn) {
if (conn == null || conn.getSource() == conn.getTarget()) {
throw new IllegalArgumentException();
}
if (conn.getSource() == this) {
sourceConnections.add(conn);
firePropertyChange(SOURCE_CONNECTIONS_PROP, null, conn);
} else if (conn.getTarget() == this) {
targetConnections.add(conn);
firePropertyChange(TARGET_CONNECTIONS_PROP, null, conn);
}
}

tag void removeConnection(Connection conn) {
if (conn == null) {
throw new IllegalArgumentException();
}
if (conn.getSource() == this) {
sourceConnections.remove(conn);
firePropertyChange(SOURCE_CONNECTIONS_PROP, null, conn);
} else if (conn.getTarget() == this) {
targetConnections.remove(conn);
firePropertyChange(TARGET_CONNECTIONS_PROP, null, conn);
}
}

tag public List getSourceConnections() {
return new ArrayList(sourceConnections);
}

tag public List getTargetConnections() {
return new ArrayList(targetConnections);
}

...
}
圖 3 圖形功能

頂層模型類(lèi)

通過(guò)上面的準(zhǔn)備,我們可以開(kāi)始編寫(xiě)頂層模型類(lèi)。Connection類(lèi)表示兩個(gè)圖形間的連接。它存儲(chǔ)連接的源和目標(biāo)。通過(guò)調(diào)用disconnectreconnect方法可以修改連接。連接含有一個(gè)boolean值來(lái)表示連接是否存在。命令會(huì)使用這個(gè)值來(lái)驗(yàn)證某種操作的合法性。源連接和目標(biāo)連接都保持一個(gè)到源圖形的引用,這樣使得被斷開(kāi)的連接可以很容易地被重新連接。連接包含一個(gè)屬性,就是線的類(lèi)型。EllipticalShapeRectangularShape類(lèi)都擴(kuò)展了Shape類(lèi),添加了很少的功能。

ShapeDiagram類(lèi)是ModelElement類(lèi)的子類(lèi),它可以作為一種容器。它維護(hù)一組圖形,并通知監(jiān)聽(tīng)器這組圖形的變化。命令可以調(diào)用tagaddChildtagremoveChild方法,并檢查返回的boolean值來(lái)驗(yàn)證它們的操作。這個(gè)類(lèi)也提供了tag公共方法給控制器類(lèi)。

public class ShapesDiagram extends ModelElement {

...
private Collection shapes = new Vector();

tag public boolean addChild(Shape s) {
if (s != null && shapes.add(s)) {
firePropertyChange(CHILD_ADDED_PROP, null, s);
return true;
}
return false;
}

tag public List getChildren() {
return new Vector(shapes);
}

tag public boolean removeChild(Shape s) {
if (s != null && shapes.remove(s)) {
firePropertyChange(CHILD_REMOVED_PROP, null, s);
return true;
}
return false;
}
}
圖 4. ShapeDiagram - 圖形的容器

實(shí)現(xiàn)上需要注意的地方

細(xì)心的讀者一定意識(shí)到這個(gè)模型創(chuàng)建了一個(gè)有向圖的實(shí)現(xiàn),圖形作為頂點(diǎn),連接作為邊,所有圖形,連接構(gòu)成的圖就是圖。這里所形成的表示方式稱為鄰接點(diǎn)列表表示法,它很適合稀疏圖。只要略作修改,這個(gè)模型的代碼就可以轉(zhuǎn)變?yōu)橐话愕膱D表示。這里對(duì)算法書(shū)中的圖實(shí)現(xiàn)所需要做的就是添加代碼使得圖,節(jié)點(diǎn),和邊在發(fā)生改變的時(shí)候發(fā)送事件。不象數(shù)學(xué)上的圖,節(jié)點(diǎn)不是零維的點(diǎn),而是有矩形邊框。最后,圖存儲(chǔ)了所有的邊,而圖形并沒(méi)有存儲(chǔ)連接,因?yàn)镚EF并沒(méi)有要求這么做。

值得注意的是,由上面的類(lèi)所提供的解決方案并不是唯一的方法。那些開(kāi)發(fā)計(jì)算機(jī)圖形的人更愿意用另一種方法來(lái)存儲(chǔ)連接,安排節(jié)點(diǎn)和邊之間的通信。然而,這些細(xì)節(jié)并不是那么重要。設(shè)計(jì)者可以自由地選擇他們認(rèn)為更具普遍性,更快,或者功能更強(qiáng)的模型表示。關(guān)鍵的地方在模型改變的消息通知,模型修改的維護(hù),包括對(duì)可視屬性和模型持久化的支持。其余的都取決于你的經(jīng)驗(yàn)和需要,你可以自由地進(jìn)行選擇。

視圖

由于這個(gè)圖形編輯器非常的簡(jiǎn)單,我們不必創(chuàng)建可視圖形來(lái)表示我們的模型,而是使用預(yù)定義的可視圖形。Figure類(lèi)加上FreeformLayout布局管理器用來(lái)表示圖。這允許我們將對(duì)象拖放到任何位置。RectangleFigureEllipse都可以表示對(duì)象。使用預(yù)定義的可視圖形來(lái)表示部分模型并不是通常的做法。即使你的視圖沒(méi)有引用模型或控制器,它都必須為每個(gè)用戶可能需要查看或修改的模型重要方面都定義可視化屬性。因此常常會(huì)定義擁有大量可視化屬性,比如顏色,文本,嵌套可視圖形等,的復(fù)雜可視圖形,每個(gè)屬性都對(duì)應(yīng)于它們所表示的模型屬性。有關(guān)創(chuàng)建復(fù)雜可視圖形的詳細(xì)處理,請(qǐng)參考 [4]。

部件(part)

對(duì)于模型的每個(gè)獨(dú)立部分,我們都必須定義控制器。所謂“獨(dú)立”,指的是這個(gè)實(shí)體都可以作為用戶操作的對(duì)象。一個(gè)比較好的原則就是任何可以被選擇,或刪除的對(duì)象都應(yīng)該有它自己的編輯部件。

編輯部件知道模型,監(jiān)聽(tīng)模型改變所產(chǎn)生的事件,然后更新視圖。由于在模型層所做的設(shè)計(jì)選擇,所有的編輯部件都必需遵循圖5所示的模式。每個(gè)部件tag都實(shí)現(xiàn)PropertyChangeListener接口。當(dāng)它被激活時(shí)tag,它將自己注冊(cè)為模型的屬性修改事件的接收者。當(dāng)失活時(shí)tag,它將自己從監(jiān)聽(tīng)器的列表中移除。最后,當(dāng)它收到屬性修改事件時(shí)tag,它會(huì)根據(jù)屬性名和新舊值來(lái)刷新表示模型的可視圖形。事實(shí)上,這個(gè)模式使用非常普遍,在大的應(yīng)用中,它會(huì)建立一個(gè)基類(lèi)來(lái)提供這樣的行為。

public abstract class SpecificPart extends AbstractGraphicalEditPart
tag implements PropertyChangeListener {

tag public void activate() {
if (!isActive()) {
super.activate();
((PropertyAwareModel) this.getModel()).addPropertyChangeListener(this);
}
}

tag public void deactivate() {
if (isActive()) {
((PropertyAwareModel) this.getModel()).removePropertyChangeListener(this);
super.deactivate();
}
}

tag public void propertyChage(PropertyChangeEvent evt) {
String prop = evt.getPropertyName();
...
}
}
圖 5. 知道屬性變化的編輯部件

DiagramEditPart 類(lèi)

當(dāng)編輯器成功載入一個(gè)幾何圖,并將它設(shè)置在一個(gè)圖形viewer上,就要求ShapesEditPartFactory創(chuàng)建一個(gè)編輯部件來(lái)控制圖。它創(chuàng)建一個(gè)新的DiagramEditPart實(shí)例,并將圖設(shè)置為它的模型。當(dāng)新創(chuàng)建的編輯部件被激活時(shí),它將自己注冊(cè)為模型的監(jiān)聽(tīng)器,并創(chuàng)建一個(gè)使用free form布局管理器的可視圖形,這種布局管理器允許通過(guò)它們的邊界來(lái)定位圖的可視圖形。DiagramEditPart通過(guò) getModelChildren方法來(lái)獲取圖中包含的所有圖形。就象前面提到的,GEF為返回的所有子模型對(duì)象都會(huì)創(chuàng)建編輯部件和可視圖形。

DiagramEditPart類(lèi)安裝了三個(gè)策略。所有的策略都在AbstractEditPart類(lèi)的createEditPolicies方法中定義,同時(shí)所有繼承自AbstractGraphicalEditPart 的實(shí)類(lèi)都必需實(shí)現(xiàn)這個(gè)方法。編輯部件使用這些策略來(lái)處理工具發(fā)出的請(qǐng)求。在最簡(jiǎn)單的情況下,策略負(fù)責(zé)生成許多命令。策略使用String類(lèi)型的索引字注冊(cè)在編輯部件上,這個(gè)索引字被稱為策略角色。這些索引字對(duì)編輯部件本身來(lái)說(shuō)沒(méi)有什么意義。然而,對(duì)軟件開(kāi)放人員,就有意義了,它使得其他人,尤其是擴(kuò)展你的控制器的人,可以通過(guò)這些索引字來(lái)關(guān)閉或移除策略。就GEF而言,你的索引字可以是“foobar”。然而,你最好告訴你程序員同伴,當(dāng)布局管理器改變的時(shí)候,為了設(shè)置新的布局策略,需要安裝新的“foobar”策略。由于這可能很有趣,且不是那么顯而易見(jiàn),所以推薦你使用EditPolicy接口定義索引字,這些名字需要很好的表達(dá)該策略在編輯部件中的角色。

安裝的第一個(gè)策略tag的索引字是EditPolicy.COMPONENT_ROLE,它負(fù)責(zé)阻止模型的根被刪除。它重寫(xiě)了createDeleteCommand方法,并返回一個(gè)不能被執(zhí)行的命令。第二個(gè)策略tag的索引字是LAYOUT_ROLE,它處理創(chuàng)建請(qǐng)求和邊界修改請(qǐng)求。當(dāng)新的圖形被放置到圖中,第一個(gè)請(qǐng)求被發(fā)送出來(lái)。布局策略返回一個(gè)命令,這個(gè)命令添加新的圖形到圖編輯器中,并把它放置在適當(dāng)?shù)奈恢?。用戶修改圖中已存在的圖形大小或移動(dòng)它時(shí),都會(huì)發(fā)出邊界修改請(qǐng)求。第三個(gè)installEditPolicy調(diào)用tag刪除一個(gè)策略。它在用戶點(diǎn)擊模型根所在區(qū)域時(shí),阻止根部件提供選擇反饋。這里也可以看出一個(gè)有意義的策略索引字的重要性。

protected void createEditPolicies() {
tag installEditPolicy(EditPolicy.COMPONENT_ROLE, new RootComponentEditPolicy());
XYLayout layout = (XYLayout) getContentPane().getLayoutManager();
tag installEditPolicy(EditPolicy.LAYOUT_ROLE, new ShapesXYLayoutEditPolicy(layout));
tag installEditPolicy(EditPolicy.SELECTION_FEEDBACK_ROLE, null);
}
圖 6. 圖編輯部件中安裝的策略 (譯:這里和Eclipse 3.1 GEF提供的例子代碼略有出入)

圖編輯部件監(jiān)視子編輯部件的添加,移除事件。當(dāng)任何新的圖形添加或移除時(shí),ShapesDiagam類(lèi)將發(fā)送這些事件。當(dāng)圖編輯部件檢測(cè)到這兩種屬性修改事件時(shí),圖編輯部件都會(huì)調(diào)用AbstractEditPart類(lèi)中定義的refreshChildren方法。這個(gè)方法會(huì)遍歷所有子模型對(duì)象,并相應(yīng)地添加,移除,或重新排序子編輯部件。

ShapeEditPart 類(lèi)

ShapeEditPart類(lèi)管理所有的圖形。當(dāng)DiagramEditPart會(huì)返回子模型列表時(shí),ShapeEditPartShapesEditPartFactory類(lèi)根據(jù)每個(gè)模型對(duì)象的類(lèi)型創(chuàng)建。工廠類(lèi)創(chuàng)建的每個(gè)部件都擁有一個(gè)它們所控制的子模型。一旦模型對(duì)象被設(shè)置,編輯部件被要求創(chuàng)建可視圖形來(lái)表示模型對(duì)象。根據(jù)模型對(duì)象的類(lèi)型,返回橢圓或矩形的編輯部件。

這個(gè)編輯部件關(guān)注四類(lèi)屬性修改事件:大小,位置,源連接,和目標(biāo)連接。如果圖形改變了大小或位置,tagrefreshVisual方法會(huì)被調(diào)用。這個(gè)方法在可視圖形被創(chuàng)建的時(shí)候就會(huì)由GEF自動(dòng)調(diào)用。在這個(gè)方法中,可視圖形的可視屬性應(yīng)該根據(jù)模型的狀態(tài)做相應(yīng)調(diào)整。重用模型更新方法是 GEF編輯器中經(jīng)常碰到的又一種模式。在我們這個(gè)編輯部件類(lèi)中,新的位置和大小被獲取并儲(chǔ)存在表示圖形的可視圖形中。此外,新的邊界會(huì)傳給父控制器的布局管理器。當(dāng)源連接或目標(biāo)連接改變時(shí),源連接或目標(biāo)連接改編輯部件會(huì)調(diào)用AbstractGraphicalEditPart類(lèi)中的方法刷新。和refreshChildren方法相似,這些方法會(huì)遍歷所有的連接,并相應(yīng)添加,刪除,或重新定位它們的編輯部件。

class ShapeEditPart extends AbstractGraphicalEditPart 
tag implements PropertyChangeListener, NodeEditPart {

tag protected List getModelSourceConnections() {
return getCastedModel().getSourceConnections();
}

tag protected List getModelTargetConnections() {
return getCastedModel().getTargetConnections();
}

tag public ConnectionAnchor getSourceConnectionAnchor(ConnectionEditPart connection) {
return new ChopboxAnchor(getFigure());
}

tag public ConnectionAnchor getSourceConnectionAnchor(Request request) {
return new ChopboxAnchor(getFigure());
}

public void propertyChange(PropertyChangeEvent evt) {
String prop = evt.getPropertyName();

if (Shape.SIZE_PROP.equals(prop) || Shape.LOCATION_PROP.equals(prop)) {
refreshVisuals();
}
if (Shape.SOURCE_CONNECTIONS_PROP.equals(prop)) {
refreshSourceConnections();
}
if (Shape.TARGET_CONNECTIONS_PROP.equals(prop)) {
refreshTargetConnections();
}
}

tag protected void refreshVisuals() {
Rectangle bounds = new Rectangle(getCastedModel().getLocation(),
getCastedModel().getSize());
figure.setBounds(bounds);
((GraphicalEditPart) getParent()).setLayoutConstraint(this, figure, bounds);
}
}
圖 7. 圖形控制器

由于圖形可以連接到其他圖形,圖形編輯部件重寫(xiě)了taggetModelSourceConnections方法和taggetModelTargetConnections方法。這兩個(gè)方法的任務(wù)就是要通知GEF有關(guān)該圖形的源連接和目標(biāo)連接。此外,ShapeEditPart實(shí)現(xiàn)了tagNodeEditPart 接口。通過(guò)實(shí)現(xiàn)這個(gè)接口,編輯部件可以定義源錨點(diǎn)和目標(biāo)錨點(diǎn),錨點(diǎn)就是圖形和連接接觸的連接點(diǎn)。邏輯電路編輯器的例子使用這個(gè)功能來(lái)指定線如何連接到一個(gè)邏輯門(mén)元件。既然圖形并沒(méi)有特定的連接點(diǎn),我們就使用包圍矩形錨點(diǎn),它將連接設(shè)置在可視圖形的包圍矩形上。如果你愿意,你可以為橢圓返回 EllipseAnchor,它將返回一個(gè)橢圓邊界上的點(diǎn)。對(duì)于更加復(fù)雜的圖形,你應(yīng)該繼承AbstractConnectionAnchor類(lèi),并實(shí)現(xiàn) getLocation方法。注意,有兩種方法需要實(shí)現(xiàn):一個(gè)使用ConnectionEditPart對(duì)象作為參數(shù),另一個(gè)使用Request對(duì)象。當(dāng)一個(gè)新的連接被創(chuàng)建時(shí),第二個(gè)方法tag會(huì)被調(diào)用以便用戶得到反饋,而第一個(gè)方法tag用于已建立的連接。

圖形編輯部件安裝了兩個(gè)策略。ShapeComponentEditPolicy提供命令將一個(gè)圖形從圖刪除。第二個(gè)策略處理圖形間連接的創(chuàng)建和轉(zhuǎn)移,它的索引字是GRAPHICAL_NODE_ROLE。連接創(chuàng)建工具創(chuàng)建新的連接需要兩個(gè)步驟。當(dāng)用戶點(diǎn)擊模型元素的可視圖形時(shí),該策略被要求tag創(chuàng)建一個(gè)連接命令。如果這個(gè)方法返回null,表示這個(gè)連接不能從所給的模型元素開(kāi)始。如果允許連接的話,將創(chuàng)建新的命令,并作為起始命令存儲(chǔ)在請(qǐng)求中。當(dāng)用戶點(diǎn)擊另一個(gè)可視圖形時(shí),會(huì)要求策略提供一個(gè)tag連接完成命令。這是一個(gè)根據(jù)起始命令創(chuàng)建的新命令,而起始命令中包含了連接結(jié)束點(diǎn)的信息。

new GraphicalNodeEditPolicy() {
tag protected Command getConnectionCreateCommand(CreateConnectionRequest request) {
Shape source = (Shape) getHost().getModel();
int style = ((Integer) request.getNewObjectType()).intValue();
ConnectionCreateCommand cmd = new ConnectionCreateCommand(source, style);
request.setStartCommand(cmd);
return cmd;
}

tag protected Command getConnectionCompleteCommand(CreateConnectionRequest request) {
ConnectionCreateCommand cmd =
(ConnectionCreateCommand) request.getStartCommand();
cmd.setTarget((Shape) getHost().getModel());
return cmd;
}

...
}
圖 8. 圖形節(jié)點(diǎn)編輯策略

圖形節(jié)點(diǎn)編輯策略的另一個(gè)任務(wù)是提供連接的轉(zhuǎn)移命令。連接可以修改連接的源或目標(biāo)實(shí)現(xiàn)轉(zhuǎn)移。連接轉(zhuǎn)移命令和連接創(chuàng)建命令有同樣的規(guī)則。尤其是當(dāng)一個(gè)連接不能轉(zhuǎn)移時(shí),策略返回null。策略也可能通過(guò)canExecute方法返回false來(lái)得到一個(gè)拒絕執(zhí)行的命令。由于篇幅限制,這些命令的細(xì)節(jié)就不多說(shuō)了,讀者可以參考代碼。

ConnectionEditPart 類(lèi)

由于連接也是用戶可編輯的模型對(duì)象,它們必須有自己的控制器。連接的控制器是由ConnectionEditPart類(lèi)實(shí)現(xiàn),它繼承自AbstractConnectionEditPart類(lèi)。和其他控制器類(lèi)似,它也實(shí)現(xiàn)了tagPropertyChangeListener接口,并注冊(cè)自己為模型的監(jiān)聽(tīng)器。連接部件tag返回一個(gè)帶有箭頭的線作為可視圖形。它安裝了兩個(gè)編輯策略。第一個(gè)是tagConnectionComponentPolicy,它提供刪除命令給Delete菜單項(xiàng)所需要的action。第二個(gè)tag比較有意思。它含有一個(gè)被選擇的連接,這個(gè)連接包括起始端和結(jié)束端的標(biāo)識(shí)。沒(méi)有這個(gè)策略,就不可能轉(zhuǎn)移連接,因?yàn)楫?dāng)一個(gè)連接被拖動(dòng)時(shí),GEF沒(méi)有辦法獲取連接兩端的標(biāo)識(shí)。GEF的設(shè)計(jì)者建議所有的ConnectionEditParts都應(yīng)該有這個(gè)策略,即使連接的兩端都不能拖動(dòng)。至少這個(gè)策略提供了一種視覺(jué)上的選擇反饋。propertyChange方法可以收到tag線風(fēng)格屬性的變化,并對(duì)線figure作相應(yīng)的調(diào)整。

class ConnectionEditPart extends AbstractConnectionEditPart 
tag implements PropertyChangeListener {

protected IFigure createFigure() {
tag PolylineConnection connection = (PolylineConnection) super.createFigure();
connection.setTargetDecoration(new PolygonDecoration());
connection.setLineStyle(getCastedModel().getLineStyle());
return connection;
}

protected void createEditPolicies() {
tag installEditPolicy(EditPolicy.CONNECTION_ROLE, new ConnectionEditPolicy() {
protected Command getDeleteCommand(GroupRequest request) {
return new ConnectionDeleteCommand(getCastedModel());
}
});
tag installEditPolicy(EditPolicy.CONNECTION_ENDPOINTS_ROLE,
new ConnectionEndpointEditPolicy());
}

public void propertyChange(PropertyChangeEvent event) {
String property = event.getPropertyName();
tag if (Connection.LINESTYLE_PROP.equals(property)) {
((PolylineConnection) getFigure()).
setLineStyle(getCastedModel().getLineStyle());
}
}
...
}
圖 9. 連接控制器

幾何圖形編輯器

幾何圖形編輯器繼承了GraphicalEditorWithFlyoutPalette類(lèi)。這個(gè)類(lèi)是圖形編輯器的一種特殊形式,它本身也是一種編輯部件,并可以擁有一個(gè)提供工具的面板。使用這個(gè)類(lèi)必須實(shí)現(xiàn)兩個(gè)方法,getPaletteRootgetPalettePreferences。第一個(gè)方法必須返回包含所有工具選項(xiàng)的面板的根節(jié)點(diǎn)。工具選項(xiàng)是一種特殊的面板選項(xiàng),它將工具安裝在編輯器的編輯域上。它們必須位于面板抽屜中,面板抽屜將工具選項(xiàng)很方便地組合起來(lái)。一般推薦有一個(gè)工具選項(xiàng)作為整個(gè)工具面板的缺省選項(xiàng)。一個(gè)典型的解決方法就是直接使用SelectionToolEntry類(lèi)的實(shí)例。第二個(gè)方法返回的面板首選項(xiàng)中包含的內(nèi)容有,報(bào)告面板是可見(jiàn)還是被折疊起來(lái)了,面板停靠的位置,以及面板的寬度。通常的解決方法是將它們存在plug-in的首選項(xiàng)存儲(chǔ)區(qū)中。

我們上面提到的編輯域起了一個(gè)中心控制器的作用。它負(fù)責(zé)保存工具,載入缺省工具,維護(hù)當(dāng)前激活的工具,并將鼠標(biāo)和鍵盤(pán)事件轉(zhuǎn)發(fā)給當(dāng)前激活的工具,以及處理命令棧。GEF提供了缺省實(shí)現(xiàn),DefaultEditDomain,你應(yīng)該在編輯器的構(gòu)造函數(shù)中設(shè)置它的實(shí)例。

圖形編輯器的另一部分工作是創(chuàng)建并初始化圖形viewer。圖形viewer是一種特殊的EditPartViewer,它能夠做點(diǎn)擊測(cè)試。我們可以使用GraphicalEditor類(lèi)提供的缺省viewer。然而,還是需要做一些事。在configureGraphicalViewer方法tag中設(shè)置編輯部件的工廠類(lèi)。這個(gè)工廠類(lèi)必須實(shí)現(xiàn)一個(gè)接口EditPartFactory,這個(gè)接口只有一個(gè)方法,createEditPart(EditPart, Object)。它的第一個(gè)參數(shù)是編輯部件,它一般是所創(chuàng)建的編輯部件的父部件,第二個(gè)參數(shù)是新創(chuàng)建的編輯部件所對(duì)應(yīng)的模型部件。其他要做的包括設(shè)置鍵處理器,上下文菜單等。

protected void configureGraphicalViewer() {
super.configureGraphicalViewer();

GraphicalViewer viewer = getGraphicalViewer();
viewer.setRootEditPart(new ScalableRootEditPart());
tag viewer.setEditPartFactory(new ShapesEditPartFactory());
viewer.setKeyHandler(
new GraphicalViewerKeyHandler(viewer).setParent(getCommonKeyHandler()));
ContextMenuProvider cmProvider =
new ShapesEditorContextMenuProvider(viewer, getActionRegistry());
viewer.setContextMenu(cmProvider);
getSite().registerContextMenu(cmProvider, viewer);
}

protected void initializeGraphicalViewer() {
super.initializeGraphicalViewer();
GraphicalViewer graphicalViewer = getGraphicalViewer();
tag graphicalViewer.setContents(getModel());
tag graphicalViewer.addDropTargetListener(createTransferDropTargetListener());
}
圖 10. 配置并初始化一個(gè)圖形viewer

一旦工廠類(lèi)被設(shè)置,你應(yīng)該在圖形viewer中tag設(shè)置內(nèi)容。內(nèi)容自然就是從IEditorInput實(shí)例恢復(fù)得到的對(duì)象,IEditorInput實(shí)例通過(guò)setInput方法傳遞給編輯器。這個(gè)例子在圖形viewer上添加tag一個(gè)目標(biāo)放置監(jiān)聽(tīng)器。它允許用戶使用拖放的方式添加新圖形,而不是選擇加點(diǎn)擊的方式。這個(gè)目標(biāo)放置監(jiān)聽(tīng)器使用TemplateTransferDropTargetListener的子類(lèi),它使用CreateRequest來(lái)獲得添加對(duì)象到模型的命令,這個(gè)模型當(dāng)然就是拖放動(dòng)作結(jié)束時(shí)所在的編輯部件所表示的模型。

除了上面談到的任務(wù),編輯器還負(fù)責(zé)監(jiān)視命令棧來(lái)報(bào)告當(dāng)前編輯的內(nèi)容是否被修改。這是一個(gè)比較好的解決方法,因?yàn)樗梢允惯@個(gè)標(biāo)記和用戶所做的undo和redo同步起來(lái)。注意,命令棧含有上次存儲(chǔ)的位置信息,這個(gè)信息在doSavedoSaveAs這兩個(gè)方法中被標(biāo)記。編輯器的其他細(xì)節(jié),比如模型的實(shí)際存儲(chǔ)和恢復(fù),這里就不討論了,因?yàn)樗鼈兒途唧w的應(yīng)用相關(guān)。接下來(lái),我們討論編輯器的如何將編輯器內(nèi)容暴露給其視圖,如何將菜單選項(xiàng)和編輯器的action聯(lián)系起來(lái),以及其他workbench協(xié)作的技術(shù)。

和workbench集成在一起

到目前為止,我們談的都是這個(gè)幾何圖形編輯器如何工作。然而,它沒(méi)有和workbench很好地集成。例如,Edit菜單動(dòng)作,比如Delete,UndoRedo,就不能工作。其他視圖不能用其他方式顯示編輯器內(nèi)容。換句話說(shuō),目前所完成的編輯器沒(méi)有很好地利用Eclipse workbench的優(yōu)勢(shì)。在下面的三小節(jié),將解釋如何將這個(gè)孤立的編輯器變成workbench的一部分。

編輯器Action

ShapesEditor類(lèi)創(chuàng)建了大量缺省動(dòng)作,它們?cè)诰庉嬈鞒跏蓟^(guò)程中被createActions方法中創(chuàng)建。這些動(dòng)作是undo,redo,select all,delete,save和print。為了將標(biāo)準(zhǔn)菜單選項(xiàng)連接到這些動(dòng)作,你應(yīng)該在plugin.xml文件中定義一個(gè)action bar contributor。在這個(gè)action bar contributor中,你需要實(shí)現(xiàn)兩個(gè)方法。第一個(gè)是tagbuildActions,它可以為undo,redo和delete創(chuàng)建可重定位的動(dòng)作。如果你需要使用鍵盤(pán)選擇所有的widget,你需要在第二個(gè)方法declareGlobalActionKeys中為所選擇的動(dòng)作tag添加一個(gè)全局動(dòng)作關(guān)鍵字。

 

public class ShapesEditorActionBarContributor extends ActionBarContributor {
tag protected void buildActions() {
this.addRetargetAction(new UndoRetargetAction());
this.addRetargetAction(new RedoRetargetAction());
this.addRetargetAction(new DeleteRetargetAction());
}

public void contributeToToolBar(IToolBarManager toolBarManager) {
super.contributeToToolBar(toolBarManager);
toolBarManager.add(getAction(ActionFactory.UNDO.getId()));
toolBarManager.add(getAction(ActionFactory.REDO.getId()));
}

protected void declareGlobalActionKeys() {
tag this.addGlobalActionKey(ActionFactory.SELECT_ALL.getId());
}
}
圖 11. 連接菜單動(dòng)作

我們來(lái)仔細(xì)看一下當(dāng)用戶在Edit菜單中選擇Delete時(shí)發(fā)生了些什么(看圖12)。ShapesEditor類(lèi)的父類(lèi)將刪除動(dòng)作添加到動(dòng)作注冊(cè)表中。當(dāng)刪除動(dòng)作被執(zhí)行時(shí),它檢查當(dāng)前的所選擇的對(duì)象是否是EditPart類(lèi)的實(shí)例。對(duì)每個(gè)對(duì)象,它都從編輯部件中請(qǐng)求一個(gè)命令。接下來(lái),每個(gè)編輯部件檢查是否有編輯策略可以處理刪除請(qǐng)求。對(duì)幾何圖形,ShapeComponentEditPolicy可以處理刪除請(qǐng)求,并且返回ShapeDeleteCommand實(shí)例。動(dòng)作執(zhí)行該命令,從而將圖形從圖中刪除。圖發(fā)送一個(gè)屬性修改事件,DiagramEditPart收到該事件,最終使得代表被刪除圖形的矩形或橢圓從顯示中被刪除。

Delete action call sequence
圖 12. 刪除動(dòng)作執(zhí)行序列

提供屬性

每個(gè)圖形編輯器都是可以發(fā)送選擇事件。你可以建立一個(gè)視圖,并將它作為選擇監(jiān)聽(tīng)器注冊(cè)在workbench site的頁(yè)面上。每次你在圖形編輯器中選擇一個(gè)對(duì)象,你的視圖都會(huì)在selectionChanged方法中收到一個(gè)通知。Eclipse的一個(gè)標(biāo)準(zhǔn)視圖,Properties視圖,會(huì)監(jiān)聽(tīng)選擇事件,并且每次都檢查這個(gè)對(duì)象是否實(shí)現(xiàn)了IPropertySource接口。如果是的話,它使用這個(gè)接口的方法來(lái)查詢所選擇的對(duì)象屬性,并以表格的方式顯示出來(lái)。

通過(guò)上面所描述的,在圖形編輯器中提供對(duì)象的屬性只要實(shí)現(xiàn)IPropertySource接口就可以了。通過(guò)查看Shape類(lèi),你可以看到對(duì)象的位置和大小是如何在Properties視圖中顯示的。

提供Outline

Outline視圖是另一種,常常也是更簡(jiǎn)潔的查看模型對(duì)象的方式。在Java編輯器中,它可以用來(lái)顯示一個(gè)類(lèi)所import的類(lèi),所包含的變量,和方法,卻不需要用戶深入代碼。圖形編輯器也可以使用這個(gè)視圖。圖形編輯器和邏輯電路編輯器類(lèi)似,可以以樹(shù)的方式顯示所編輯的內(nèi)容(看圖1)。數(shù)據(jù)庫(kù)schema編輯器[7]也提供了類(lèi)似的視圖。

為了將所編輯的內(nèi)容提供給Outline視圖,你需要重寫(xiě)getAdapter方法,并當(dāng)adapter類(lèi)為IContentOutlinePage接口時(shí),返回一個(gè)outline實(shí)現(xiàn)。實(shí)現(xiàn)outline的最簡(jiǎn)單的方法是擴(kuò)展ContentOutlinePage類(lèi),并提供適當(dāng)?shù)?code>EditPartViewer。

public Object getAdapter(Class type) {
// returns the content outline page for this editor
tag if (type == IContentOutlinePage.class) {
if (outlinePage == null) {
outlinePage = new ShapesEditorOutlinePage(this, new TreeViewer());
}
return outlinePage;
}
return super.getAdapter(type);
}
圖 13. 提供一個(gè)outline

在我們這個(gè)例子中,編輯部件視圖是有一個(gè)TreeViewer實(shí)現(xiàn)的。你應(yīng)該和主編輯器一樣提供給它同樣的編輯域。TreeViewer,就象其他EditPartViewer,需要一個(gè)創(chuàng)建子編輯部件的方法。編輯器和DiagramEditPart一樣,都是設(shè)置一個(gè)編輯部件工廠。此外,outline中的選擇和主編輯窗口的選擇需要通過(guò)選擇同步器同步起來(lái),選擇同步器是一個(gè)GEF工具類(lèi),它協(xié)調(diào)兩個(gè)編輯部件的選擇狀態(tài)。ShapesTreeEditPartFactory根據(jù)模型類(lèi)型,返回ShapeTreeEditPartDiagramTreeEditPart的實(shí)例。通過(guò)這些類(lèi),讀者應(yīng)該可以輕易地發(fā)現(xiàn)這些模式很熟悉。兩個(gè)編輯部件都實(shí)現(xiàn)了PropertyChangeListener接口,并通過(guò)調(diào)整模型的可視化表示來(lái)對(duì)屬性變化做出響應(yīng)。它們都安裝編輯策略來(lái)控制通過(guò)它們所暴露的交互類(lèi)型。

GEF用到的設(shè)計(jì)模式

GEF通過(guò)大量使用設(shè)計(jì)模式來(lái)得到它的靈活性。下面是一下經(jīng)常碰到的模式的小結(jié)。詳細(xì)內(nèi)容,請(qǐng)參考 [2]。

模型-視圖-控制器(Model-View-Controller )
MVC模式被GEF用來(lái)解除用戶界面,行為和表示之間的耦合。模型可以用任何Java對(duì)象來(lái)表示。視圖必須實(shí)現(xiàn)IFigure接口。控制的類(lèi)型必須是EditPart或它的子類(lèi)。
命令(Command )
命令封裝了模型修改,因此支持可撤銷(xiāo)的操作。
責(zé)任鏈(Chain of Responsibility )
責(zé)任鏈通過(guò)將請(qǐng)求傳遞給多個(gè)對(duì)象,并給這些對(duì)象機(jī)會(huì)處理請(qǐng)求,從而將請(qǐng)求的發(fā)送者和接受者解除耦合。在GEF中,多個(gè)編輯策略可以收到請(qǐng)求,返回Commands,這些Commands以鏈的方式組織在一起。
狀態(tài)(State )
允許編輯器在內(nèi)部狀態(tài)發(fā)生改變的時(shí)候,修改編輯器的行為。對(duì)于GEF編輯器,用戶切換工具可以改變編輯器的狀態(tài)。例如,對(duì)于鼠標(biāo)按下事件,編輯器在激活選區(qū)工具和激活創(chuàng)建工具下的行為是截然不同的。
抽象工廠(Abstract Factory )
提供接口創(chuàng)建一系列相關(guān)或相依賴的對(duì)象。這個(gè)模式在根據(jù)模型部件創(chuàng)建編輯部件時(shí)被使用。
工廠方法(Factory Method )
定義了方法創(chuàng)建對(duì)象,但是允許子類(lèi)決定實(shí)例化的類(lèi)。這個(gè)模式?jīng)]有被單獨(dú)討論,但是它是創(chuàng)建編輯部件的另一種可選的方法。createChild方法允許你不使用工廠就創(chuàng)建子編輯部件。

總結(jié)

我希望能夠?qū)@個(gè)簡(jiǎn)單圖形編輯器的大多數(shù)方面作詳細(xì)的描述。提供足夠的信息使得人們能夠讀完這篇文章,去看更大的例子,比如邏輯電路編輯器。通過(guò)理解象CircuitEditPart,AndGateFigure和其他類(lèi)的角色,你可以關(guān)注其他例子的更復(fù)雜的方面。在GEF的眾多領(lǐng)域和技術(shù)中,有很多我甚至都沒(méi)有涉及過(guò)。然而,這些技術(shù)只有在很好地理解基礎(chǔ)內(nèi)容的情況下,才可能去學(xué)習(xí)。畢竟,如果你為了使Select All菜單項(xiàng)工作都要花數(shù)小時(shí),那么設(shè)計(jì)一個(gè)拖反饋的目的又是什么呢?

感謝

我想感謝Randy Hudson,他的意見(jiàn)幫助提高了本文結(jié)構(gòu)和準(zhǔn)確性。我也感謝Jill Sueoka仔細(xì)檢查我所寫(xiě)一個(gè)又一個(gè)版本。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多