我们先解决如下问题:
在界面操作过程中,我们希望面对的是边框调整事件,图形移动事件,或者是编辑文本事件,而不是原始的鼠标和键盘事件?
术语定义:
系统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问题。