这几天一直在做GEF的一个项目,看过网上的一些资料,觉得能为一些图形元素添加一个工具条,那将大大提高用户体验。网上讨论相关问题的好像只有刘刚,但是相比较他的实现,我的想法不同,我的想法是参考每一个Figure的toolTip的实现,通过shell来显示这个工具条,这样,我们就不必在palette和图形工作区之间来回操作了。下面就说说我的想法和实现。这里需要注明的一点是:我所说的“图形元素”是指那些非Connection的GEF图形,比如GEF例子Shape中的RectangleFigure和EllipseFigure,Logic中的LED、AndGate、OrGate等等;或者UML工具中的类图、活动图、状态机中的状态等等;例如:
这个图中的Work Flow Item就是我所说的图形元素,而上面的Work Flow Path其实就是两个图形元素之间的Connection;
我们都知道Figure是可以设置toolTip的,这个toolTip是显示在Figure之上的,那么GEF是怎么实现的呢?下面先说一下toolTip是如何实现的。
toolTip的显示是由ToolTipHelper类实现的,这个类继承自PopUpHelper,由这个图可以看出,PopUpHelper有自己的lws和自己的shell,PopUpHelper完全利用了LightweightSystm和相应的shell来实现弹出式窗口的显示,显示是通过show方法实现的,同时这个类里还有一个Control变量,这个变量表示当前弹出式窗口所属那个SWT Control。通过这个启发,我们可以实现任何形式的地弹出式窗口,如果我们愿意,我们甚至可以在这个窗口里显示表格、按钮甚至树型结构等等;而toolTip仅仅是一个简简单单的实现而已。
那么需要实现浮动工具条的时候,我们需要做以下几个方面的工作:
2、工具条的显示应该受几个方面的限制:
如果鼠标移动到工具条上,工具条应该始终显示;
如果鼠标没有进入到工具条上,那么不论鼠标是Hover还是Move,工具条都应该显示一定的时间,然后消失;
3、如果在工具条上选中了某一个工具,那么应该构造相应的Command,这个Command可以是普通的Command用来构造图形元素,也可以是ConnectionCommand用来在两个图形元素之间构造Connection;只是这两个Command是有区别的,前者可以直接执行,而后者由于需要一个目标的图形元素,因此命令的执行需要两个阶段,首先要构造一个Start Command,然后当选定了某个目标图形元素后“Complete Command”,其实这就是EditPolicy.GRAPHICAL_NODE_ROLE这个角色名定义的默认操作。
4、zoom实现对坐标位置的影响;
1、对于以上讨论中的第一点,我们必须实现图形元素对应Figure的MouseMotionListener,并在MouseHover方法中实现:
LightweightSystm的构造;
Shell的构造;
浮动工具条对应Figure的构造;
在合适的位置显示shell;-- shell.setVisible(true);
创建Timer,由这个定时器控制浮动工具条的显示与否;
以上工作的前4步是标准的Draw2D程序,只要懂Draw2D,应该没有问题。但是这里需要注意以下几点:
·Shell在构造的过程中,往往需要一个parent,这个parent是如何获得的呢?---- 我们可以在相应的EditPart中调用getViewer().getControl()得到Control后然后调用其getShell ()方法得到parent;其实我们动态跟踪ToolTipHelper的执行会发现,这就是它的实现方法;
·shell显示位置的确定,必须是屏幕上的绝对位置,为了这件事,我鼓捣了半天。尤其是多屏幕的时候,如果处理的不对,这种错误更加明显,明明程序运行在分屏的一个屏幕上,可是shell却显示在另外一个屏幕上,实在让人哭笑不得。这里最简单的一个处理方法就是将PopUpHelper和ToolTipHelper的实现拷贝过来就可以了;
·Timer需要定义为轮询形势,也就是每隔一定时间执行一次;如下。其中3000代表3秒钟。第一个3000表示3秒后第一个执行,第二个3000表示其后每隔3秒执行一次;
timer = new Timer(true);
timer.schedule(new TimerTask() {
public void run() {
Display.getDefault().syncExec(new Thread() {
public void run() {
hide();
timer.cancel();
floattingFigure = null;
}
});
}
}, 3000, 3000);
//这个方法通过判断鼠标是否进入工具条来决定是否继续显示它,注意entered变量;
protected void hide() {
if (shell != null && !shell.isDisposed() && !entered)
shell.setVisible(false);
}
以上就是我们关心的图形元素需要做的所有工作;如下图所示,鼠标在截图时看不见;
2、对于工具条来说,它需要实现MouseListener和MouseMotionListener,前者完成在点选某个工具项时Command的构造,而后者完成鼠标是否进入工具条的判断;由此可知我们需要一个boolean变量entered来表示鼠标是否进入了工具条,进入则为true、离开就为false;这样呼应上面的实现,每次定时器执行时,都会判断entered是否为true,如果“是”则继续显示,否则将会隐藏shell;
3、对于MouseMotionListener的实现,我们可以考虑两种情况,也就是上面所说的两种类型的Command,如果是第一种Command,我们可以在浮动工具条的mousePressed方法中直接构造这个方法,然后调用命令栈去执行他:getViewer().getEditDomain().getCommandStack().execute(command);那么相应的命令立刻执行,并可以Undo;如果我们用过Eclipse3.5 Galileo Modller的话,会发现,他的UML2插件的实现就是这样的;当鼠标在工作区上停留时,会显示一个工具条,根据当前构造的UML图的不同,工具条上的可选工具也是不一样的,一旦某个工具被选中并且点击,相应的图形元素就被创建了;但是对于第二种情况,也就是ConnectionCommad,处理可就不是那么简单了,我们需要在mousePressed方法中创建“start command”,然后在mouse Released方法中将这个Command补全,也就是完成Complete Command,然后再执行;
·这里需要注意的是,由于这是工具条的鼠标事件,所以“事件”Event中的鼠标位置是相对于工具条坐标原点的,里面需要一个转化过程。
·其二,如果你的GEF程序中支持Zoom,那么当鼠标释放时,真正鼠标移动的距离(相对于工具条原点的偏移量)应该是鼠标位置与zoom率的比值,记住是比值,不是乘积。
刘刚对于创建助手的实现,如果是第一种情况,他将工具条放在了Handle层里,如果是第二种实现(连接),他是将editdomain的activeTool设定为connection的tool,然后指定targetEditPart,我还没有尝试过,不知道该如何实现;但是不管怎么说,两种方法都能实现。我们可以根据喜好选择不同的方法;