RCP UNDO和Redo

来源:互联网 发布:低价位耳机推荐知乎 编辑:程序博客网 时间:2024/05/01 09:35
 

RCP中undo/redo的实现

研究了一个多星期终于算是比较清楚了,现在写下来当作笔记。关于这些东西资料并不是很多,主要就是靠源代码和eclipse自带的帮助了,论坛方面我觉得也就是eclipse的新闻组和EZ比较有用,当然还有水木的eclipse版,有很多大牛,比如cloudor和surfeit,总是很热心地解决大家的问题。

首先要了解RetargetAction的概念,这个说起来很简单,就是一些常用action的抽象,比如copy/paste、delete以及本文的主题undo/redo,platform认为这是各个part的常用功能,如果每个part都创建自己的action是比较浪费资源的,所以就弄出了RetargetAction这个概念,它们从UI角度看只创建一遍,没有具体的功能;每个part可以提供自己的handler注册到这些RetargetAction上,这样当不同的part处于活动状态时就能够通过相同的菜单/按钮提供不同的功能。具体的类有两个,org.eclipse.ui.actions.RetargetAction和它的子类org.eclipse.ui.actions.LabelRetargetAction,主要用的是后者,因为可以动态的改变action的标签。undo/redo都有自己对应的现成的RetargetAction,就是用org.eclipse.ui.actions.ActionFactory的静态工厂方法生成,还附赠图标。

至于undo/redo所对应的handler就比较简单了,调用一次就undo/redo一次,但一些细节却困扰了我好一阵子,就是二者之间的状态转化:初始化的时候二者都应该是disabled的,然后有了undoable操作之后undo应该就要被enable了,而一旦有了undo操作之后redo也应该相应的被enable,同时当不可undo/redo的时候,而这又应该相应的被disable掉。这是一种简单的线性关系,还没有考虑到undo操作的作用域。当时我一直想不通的就是undo/redo之间状态互动应该如何实现,比如第一次undo之后如何刷新redo的状态?如果二者之间互相持有对方的引用未免使得耦合度太高了,显然应该有一个“中央控制机构”的存在。在翻遍互联网之后我终于在一个角落里找到了答案:eclipse已经为undo操作专门实现了org.eclipse.ui.operations这整整一个包的类,里面的UndoActionHandler就是现成的handler...神奇之处在于它们的父类有boolean shouldBeEnabled()这么一个抽象方法,简单用一句话就能实现状态的转化。不过我还没有搞清楚这是怎么一个调用层次,后面的机制太复杂了。

有了这些东西之后我们的工作只剩下生成undoable操作了,这些在帮助里写得很直白,也是我唯一看了一遍就理解了的部分- -0每当出现有产生undo需要的操作时,就在editor里面实现一个org.eclipse.core.commands.operations.AbstractOperation的匿名子类,其中execute方法直接把原来的操作搬进来就是了,要注意把相应的块级变量声明成final;undo就是逆操作了,这涉及到程序的具体逻辑,要仔细考虑;而redo则是undo的匿操作,我是直接return execute( monitor, info );了。

有了undoable操作之后就可以用IOperationHistory执行它了,后面的入栈操作以及状态转化都会自动搞定。这里需要注意的是之前要给操作tag一个context:所谓的context其实只是一个标签,帮助里面花了很大篇幅解释为什么要搞出这么一个玩意儿,其实也就是一句话:有些操作要求全局可逆,而另外一些则是每个part私有的,所以需要有一个context来标记作用范围。这个也是跟具体逻辑有关的,比如我搞得东西就需要操作只限于在各个editor内可逆,所以就要为每个editor搞一个自己的ObjectUndoContext

说到这里好像已经万事俱备了,UI上面放RetargetAction,通过相应的handler调用我们自己搞出来的undoable operation,不过最后的问题是:这些代码都要放在哪里。 我觉得这也是插件开发里面一个比较棘手的问题,很多类都是互相持有引用,但由于调用栈的关系会有问题,比如在我的程序里,我想让undo/redo成为editor自己的按钮,也就是说要放在EditorActionBarContributor里面,而在它被初始化时page成员的activeEditorReference和actionPartReference都是空的,花了一大把时间研究源码和调用栈之后我放弃了在contributor里面注册action的想法;看起来为时过早。但是不注册的话就无法使用快捷键;一个程序的undo操作如果不和Ctrl+Z绑定的话是很不方便的,起码多我来说是这样。这些常用的快捷键在org.eclipse.ui的插件描述中已经预置了,只要key binding用的是org.eclipse.ui.defaultAcceleratorConfiguration这个scheme,并且action是工厂造出来的(设置了对应的command id),那么只需注册他们,绑定工作就完成了。到底这些contribute-by-editor的action该怎么注册?痛苦了数日之后我终于发现只要在ActionBarAdvisor里面生成action并注册就可以了,虽然他们和添加到UI上的action并不是相同的对象,具体的原理我还没搞清楚,不过从WorkbenchWindow类的源码里面看来注册action显然不仅仅用到了id。至于handler,我也放在了editor里面生成,虽然这样好像会有很多重复,但是没办法:contributor那时候好像site这个参数也还拿不到= =

原创粉丝点击