昨天讨论了在图形元素上显示工具条的方法,应该说工作的还不是很完美,因为在选定了创建Connection的工具后,并不能像使用palette那样,在鼠标移动的过程中,有一个连接动态跟随,当鼠标释放后,如果释放位置在一个图形元素之上,那么将建立这个Connection,如果不在,那么这个连接将自动消失;今天想讨论一下如何实现这个功能;
我们知道GEF是以Draw2D的LightweightSystem作为实现的基础的,在LightweightSystm中对于mouseDown和mouseMove的实现如下:
事件发布
1
/**
@see
MouseListener#mouseDoubleClick(MouseEvent)
*/
2
public
void
mouseDoubleClick(MouseEvent e) {
3
getEventDispatcher().dispatchMouseDoubleClicked(e);
4
}
5
6
/**
@see
MouseListener#mouseDown(MouseEvent)
*/
7
public
void
mouseDown(MouseEvent e) {
8
getEventDispatcher().dispatchMousePressed(e);
9
}
10
11
/**
@see
MouseTrackListener#mouseEnter(MouseEvent)
*/
12
public
void
mouseEnter(MouseEvent e) {
13
getEventDispatcher().dispatchMouseEntered(e);
14
}
15
16
/**
@see
MouseTrackListener#mouseExit(MouseEvent)
*/
17
public
void
mouseExit(MouseEvent e) {
18
getEventDispatcher().dispatchMouseExited(e);
19
}
20
21
/**
@see
MouseTrackListener#mouseHover(MouseEvent)
*/
22
public
void
mouseHover(MouseEvent e) {
23
getEventDispatcher().dispatchMouseHover(e);
24
}
25
26
/**
@see
MouseMoveListener#mouseMove(MouseEvent)
*/
27
public
void
mouseMove(MouseEvent e) {
28
getEventDispatcher().dispatchMouseMoved(e);
29
}
30
31
/**
@see
MouseListener#mouseUp(MouseEvent)
*/
32
public
void
mouseUp(MouseEvent e) {
33
getEventDispatcher().dispatchMouseReleased(e);
34
}
35
36
/**
@see
DisposeListener#widgetDisposed(DisposeEvent)
*/
37
public
void
widgetDisposed(DisposeEvent e) {
38
getUpdateManager().dispose();
39
}
40
由此可见,Draw2D对于鼠标事件的处理是由Event Dispacher进行派发的。那么这些派发的事件是怎么处理的呢?在org.eclipse.gef.tools.AbstractTool类中有很多相应的handleXXX方法,例如:handleDoubleClick、handleDrag方法等等,这就是处理这些鼠标事件的位置,当然这些方法在这个类中,没有做什么处理,仅仅返回了false;但是如果我们继续向下查找的话,会发现其子类AbstractConnectionCreationTool重写了一些handle方法,其中之一就是handleDrag,而handleDrag调用了handleMove;
handleXXX的实现
/**
*
@see
org.eclipse.gef.tools.AbstractTool#handleDrag()
*/
protected
boolean
handleDrag() {
if
(isInState(STATE_CONNECTION_STARTED))
return
handleMove();
return
false
;
}
/**
*
@see
org.eclipse.gef.tools.AbstractTool#handleDragInProgress()
*/
protected
boolean
handleDragInProgress() {
if
(isInState(STATE_ACCESSIBLE_DRAG_IN_PROGRESS))
return
handleMove();
return
false
;
}
/**
*
@see
org.eclipse.gef.tools.AbstractTool#handleFocusLost()
*/
protected
boolean
handleFocusLost() {
if
(isInState(STATE_CONNECTION_STARTED)) {
eraseSourceFeedback();
eraseTargetFeedback();
setState(STATE_INVALID);
handleFinished();
}
return
super
.handleFocusLost();
}
/**
*
@see
org.eclipse.gef.tools.TargetingTool#handleHover()
*/
protected
boolean
handleHover() {
if
(isInState(STATE_CONNECTION_STARTED))
updateAutoexposeHelper();
return
true
;
}
/**
*
@see
org.eclipse.gef.tools.TargetingTool#handleInvalidInput()
*/
protected
boolean
handleInvalidInput() {
eraseSourceFeedback();
setConnectionSource(
null
);
return
super
.handleInvalidInput();
}
/**
*
@see
org.eclipse.gef.tools.AbstractTool#handleMove()
*/
protected
boolean
handleMove() {
if
(isInState(STATE_CONNECTION_STARTED)
&&
viewer
!=
getCurrentViewer())
return
false
;
if
(isInState(STATE_CONNECTION_STARTED
|
STATE_INITIAL
|
STATE_ACCESSIBLE_DRAG_IN_PROGRESS)) {
updateTargetRequest();
updateTargetUnderMouse();
showSourceFeedback();
showTargetFeedback();
setCurrentCommand(getCommand());
}
return
true
;
}
看到handleMove中的那个showSourceFeedback()方法了吧,其实我们今天讨论的问题,在GEF框架中就是通过它实现的;爬源码,我们可以知道这个方法事实上是调用了AbstractEditPart的showSourceFeedback()方法,而后者又调用了GraphicalNodeEditPolicy的showSourceFeedback方法,这个方法又调用了showCreationFeedback方法;
/**
* Shows feedback during a creation.
*
*
@param
request
* CreateConnectionRequest
*/
protected
void
showCreationFeedback(CreateConnectionRequest request) {
FeedbackHelper helper
=
getFeedbackHelper(request);
Point p
=
new
Point(request.getLocation());
helper.update(getTargetConnectionAnchor(request), p);
}
/**
* calls {
@link
#showCreationFeedback(CreateConnectionRequest)} when
* appropriate.
*
*
@see
org.eclipse.gef.EditPolicy#showSourceFeedback(Request)
*/
public
void
showSourceFeedback(Request request) {
if
(REQ_CONNECTION_END.equals(request.getType()))
showCreationFeedback((CreateConnectionRequest) request);
}
这下我们就明白了,我们只要照猫画虎,在我们的工具条的mouse move方法中实现上面的那个showCreationFeedback方法就行了;以下就是我们的实现:
1
public
void
mouseDragged(MouseEvent me) {
3
request
=
new
CreateConnectionRequest();
4
ScalableFreeformRootEditPart root
=
(ScalableFreeformRootEditPart) getSourceEditPart().getRoot();
5
double
zoom
=
root.getZoomManager().getZoom();
6
7
request.setLocation(
new
Point(hoverX
*
zoom, hoverY
*
zoom));
8
request.setType(
"
connection start
"
);
9
10
FeedbackHelper feedbackHelper
=
new
FeedbackHelper();
11
Point p
=
new
Point(me.x, me.y);
12
Connection connectionFeedback
=
FigureFactory.createNewWorkflowPath(
null
);
13
14
connectionFeedback.setConnectionRouter(((ConnectionLayer) getLayer(LayerConstants.CONNECTION_LAYER)).getConnectionRouter());
15
connectionFeedback.setSourceAnchor(getSourceConnectionAnchor(request));
16
feedbackHelper.setConnection(connectionFeedback);
17
18
IFigure figure
=
getLayer(LayerConstants.FEEDBACK_LAYER);
19
for
(
int
i
=
0
; i
<
figure.getChildren().size(); i
++
) {
20
if
(figure.getChildren().get(i)
instanceof
Connection) {
21
figure.remove((IFigure) figure.getChildren().get(i));
22
}
23
}
24
getLayer(LayerConstants.FEEDBACK_LAYER).add(connectionFeedback);
25
request.setLocation(
new
Point(me.x
*
zoom, me.y
*
zoom));
26
feedbackHelper.update(
null
,
new
Point(me.x
+
offsetX
*
zoom, me.y
+
offsetY
*
zoom));
27
}
28
至此,我们的问题就解决了。但是还有一个问题,那就是,如果鼠标被释放了,那么这个被放置在反馈层的Connection应该被擦除掉,而同时还应该根据鼠标的位置决定是不是应该创建Connection;所以我们还应该再改一下mouse released方法,增加如下处理:
IFigure feedBackLayer = getLayer(LayerConstants.FEEDBACK_LAYER);
feedBackLayer.getChildren().clear();
2010-08-05更正,以上部分不正确,应该是:
1
IFigure feedBackLayer
=
getLayer(LayerConstants.FEEDBACK_LAYER);
2
3
for
(
int
i
=
0
; i
<
feedBackLayer.getChildren().size(); i
++
) {
4
if
(feedBackLayer.getChildren().get(i)
instanceof
Connection) {
5
feedBackLayer.remove((IFigure) feedBackLayer.getChildren().get(i));
6
}
7
}
8
......
通过以上的处理,我们就可以实现类似于palette中Connection Tool的功能了。当我们在一个图形元素上Hover时,首先程序先将与悬停位置最近的锚点作为源锚点,并显示工具条,当我们点击工具条上的Connection图标时,随着鼠标的拖动,一个Connection将动态跟随鼠标的移动被画出来,随着鼠标的释放,Connection将根据释放位置决定是否建立一个Connection,还是将这个动态的Connection清除掉;