郁悶!同志們沒有留言,不知是寫的太差,何處需要改進,還是大家覺得對自己沒有用處....
GEF源碼分析(四) GEF 消息轉義 ___ Tool的作用
接著上一節:
我們先解決如下問題:
在界面操作過程中,我們希望面對的是邊框調整事件,圖形移動事件,或者是編輯文本事件,而不是原始的鼠標和鍵盤事件?
術語定義:
系統UI事件: 一般由某些計算機人機交互硬件發出信號,并且經操作系統理解后產生的事件,例如:原始的鼠標的移動、點擊和鍵盤的敲擊事件等。
UI業務語義事件: 由系統UI事件和其它UI業務語義事件觸發,由UI業務代碼理解后產生的事件(操作系統不可理解,同時無任何領域的業務語義),如2D圖形的尺寸調整,2D圖形的點擊,2D圖形的移動。
(其實不僅限于事件的轉義,包括UI的改變,也需要由系統UI語義轉換到UI業務語義來使用)
UI業務語義事件在Controller控制中的優點是易于理解、維護和擴充,具體的主要有以下方面:
a.減少系統UI事件判斷邏輯部分代碼的重復。比如在Controller部分代碼無需分步著大量邏輯重復的代碼,當鼠標移動時去判斷究竟是一個2D圖形的尺寸改變,還是一個2D圖形的移動,還是一組2D圖形的移動,還是2D圖形DragAndDrop的動作。
b.Controller部分的代碼語義明確。在Controller內部處理代碼和接口中直接面對的是UI業務語義,如圖形的移動,圖形的尺寸改變,這樣、代碼易于理解、調試等。
b.Controller部分代碼無需為系統UI事件保存狀態。如2D圖形的移動中,如果這些事件直接在Controller中處理,從開始MouseDown,到MouseMove,到MouseUp過程中,在Controller中要集中保留這樣眾多狀態,極易造成Controller代碼具體多頭職責的癥狀,從而患上嚴重的精神分裂:)。
于是乎GEF的FrameWork有給出以下的解決方案:

圖A
如上圖A:
GEF通過Tools(根interface是Tool),當然在視圖上的Menu和Toolbar是通過Action發出,這個是eclipse的Workbench機制,在此就不討論了。以下是Tool的類繼承樹:
圖B
從上圖B可以看出
從Tool繼承的這些類均處理一個或者一類UI業務語義事件,其中需要保留中間狀態的子類均以XXXTracker形式出現。
首先這些Tool的實現類通過Tool的接口(上圖右邊),接收界面來的事件,然后調用內部handleXXX,這樣通過內部函數getTargetRequest()[createTargetRequest()]和getSourceRequest()[createSourceRequest()],把系統UI事件轉換為UI業務語義事件叫Request(如圖C)。至此Tool會通過調用相關聯的EditPart的接口將UI業務語義事件通知到EditPart中。于是在Controller(EditPart)中僅僅需要理解處理Request對象。
圖C
下面以ResizeTracker代碼為例:
class ResizeTracker
protected Request createSourceRequest() {
ChangeBoundsRequest request;
request = new ChangeBoundsRequest(REQ_RESIZE);
request.setResizeDirection(getResizeDirection());
return request;
}
protected Command getCommand() {
List editparts = getOperationSet();
EditPart part;
CompoundCommand command = new CompoundCommand();
command.setDebugLabel("Resize Handle Tracker");//$NON-NLS-1$
for (int i = 0; i < editparts.size(); i++) {
part = (EditPart)editparts.get(i);
command.add(part.getCommand(getSourceRequest()));
}
return command.unwrap();
}
protected void eraseTargetFeedback() {
if (!getFlag(FLAG_TARGET_FEEDBACK))
return;
if (getTargetEditPart() != null)
getTargetEditPart().eraseTargetFeedback(getSourceRequest());
setFlag(FLAG_TARGET_FEEDBACK, false);
}
protected boolean handleButtonUp(int button) {
if (stateTransition(STATE_DRAG_IN_PROGRESS, STATE_TERMINAL)) {
eraseSourceFeedback();
eraseTargetFeedback();
performDrag();
}
return true;
}
最后還有個問題是

Tool這些事件接口,如何從Viewer傳遞過來的呢?
我畫了一個簡要的類圖

圖D
參照上圖,下面羅列重要代碼:
大家主要以下兩方面
一、創建這些關鍵事件處理鏈的過程(注意圖上的粗體文字);
二、消息從SWT中Canvas實例傳遞到Tool過程(圖D中注釋標簽的順序);
class GraphicalEditor
public void createPartControl(Composite parent) {
createGraphicalViewer(parent);
}
protected void createGraphicalViewer(Composite parent) {
GraphicalViewer viewer = new ScrollingGraphicalViewer();
viewer.createControl(parent);
setGraphicalViewer(viewer);
configureGraphicalViewer();
hookGraphicalViewer();
initializeGraphicalViewer();
}
protected void setGraphicalViewer(GraphicalViewer viewer) {
getEditDomain().addViewer(viewer);
this.graphicalViewer = viewer;
}
class EditDomain
public void addViewer(EditPartViewer viewer) {
viewer.setEditDomain(this);
if (!viewers.contains(viewer))
viewers.add(viewer);
}
class GraphicalViewerImpl
public void setEditDomain(EditDomain domain) {
super.setEditDomain(domain);
// Set the new event dispatcher, even if the new domain is null. This will dispose
// the old event dispatcher.
getLightweightSystem()
.setEventDispatcher(eventDispatcher = new DomainEventDispatcher(domain, this));
}
private final LightweightSystem lws = createLightweightSystem();
class ScrollingGraphicalViewer
public void setEditDomain(EditDomain domain) {
super.setEditDomain(domain);
// Set the new event dispatcher, even if the new domain is null. This will dispose
// the old event dispatcher.
getLightweightSystem()
.setEventDispatcher(eventDispatcher = new DomainEventDispatcher(domain, this));
}
class LightweightSystem
public void setEventDispatcher(EventDispatcher dispatcher) {
this.dispatcher = dispatcher; //在此Shapes例子中是DomainEventDispatcher
dispatcher.setRoot(root);
dispatcher.setControl(canvas);
}
protected void addListeners() {
EventHandler handler = createEventHandler();
canvas.getAccessible().addAccessibleListener(handler);
canvas.getAccessible().addAccessibleControlListener(handler);
canvas.addMouseListener(handler);
canvas.addMouseMoveListener(handler);
canvas.addMouseTrackListener(handler);
canvas.addKeyListener(handler);
canvas.addTraverseListener(handler);
canvas.addFocusListener(handler);
canvas.addListener(SWT.MouseWheel, handler);
……
setEventDispatcher(getEventDispatcher());
}
class EventHandler
implements MouseMoveListener, MouseListener, AccessibleControlListener, KeyListener,
TraverseListener, FocusListener, AccessibleListener, MouseTrackListener,
Listener
{
…
public void keyPressed(KeyEvent e) {
//在此Shapes例子中是DomainEventDispatcher
getEventDispatcher().dispatchKeyPressed(e);
}
…
public void mouseDoubleClick(MouseEvent e) {
getEventDispatcher().dispatchMouseDoubleClicked(e);
}
…
public void mouseMove(MouseEvent e) {
getEventDispatcher().dispatchMouseMoved(e);
}
public void mouseUp(MouseEvent e) {
getEventDispatcher().dispatchMouseReleased(e);
}
…
}
這樣、在圖A中Request發送到EditPart機制做了簡要的探討。
學習了Tool產生Request的機制,可以幫助我們調試GEF,同時如果在大家自己的程序中需要有新的UI交互類型,也可以試著建立自己的Tool類產生新的Request,這樣在自己的EditPart產生對應的Command,這樣就可以處理實現新的UI交互類型。
在下一篇我們探討一下有關圖A中的EditPart如何產生Command,以及執行Command問題。