Eclipse RCP: Declarative Actions

在做一个Eclipse RCP的项目,遇到个问题。
客户需要在一个Tree上实现popup menu,这其中的menu item根据右键点击的树节点不同而不同。用popup menu的extension很容易实现,只要加一个objectContribution就行了。根据不同的tree node类型,显示不同的action。
另一个需求很麻烦,menu item还需要根据用户所在的perspective不同而不同。objectContribution没有这么细致的声明项,似乎只有编程完成。
Popup menu actions可以实现两个接口,除了普通的声明式Action的IWorkbenchWindowActionDelegate之外,还有 IObjectActionDelegate。Addison Wesley的Eclipse RCP书中说,这个接口的setActivePart()方法在每次打开popup menu的时候都要调用,可以在这时候把不需要的Actions disable掉。试验了一下,不好使,怪?而且普通的IWorkbenchWindowActionDelegate的 selectionChanged()方法似乎也不好使。
经过对Eclipse UI源代码的挖掘,终于搞清楚了,感觉这几乎是Eclispe RCP的一个pitfall。
IActionDelegate(IWorkbenchWindowActionDelegate的父接口)的文档中说,“The workbench will generate a proxy action object on behalf of the plug-in to avoid having to activate the plug-in until the user needs it. If the action is performed the workbench will load the class that implements this interface and create what is called an action delegate object.”这话说得似乎很有道理,然而其中一个side-effect就是写在Delegate的回调方法不能轻易被调用。先要弄清楚什么是 proxy action,什么是delegate object。Proxy action是用户实际在UI中看见的传统JFace Action。比如一个menu item和toolbar上的一个按钮。Delegate Object是我们写的IActionDelegate的,实际给Declarative Action的实现。问题在这里:无论是setActivePart(),还是selectionChanged(),都是我们写在delegate object里的,而action不perform,这个object就不会被创建,当然我们的方法就不会被执行了。
看看源代码:
org.eclipse.ui.internal.PluginAction#runWithEvent():
 
if  (delegate  ==   null {
  createDelegate();
  
// ...
}

delegate.run(
this );
 
大多数情况下是这个方法调用我们写的action delegate的。很明显,直到不得不用上我们的action delegate,框架才会去生成它。
 
然后看看IObjectActionDelegate的setActivePart(),当右键菜单将要弹出的时候,触发JFace的 MenuManager的menuAboutToShow事件,事件流到 org.eclipse.ui.internal.ObjectActionContributor#contributeObjectActions(), 这是我们关心的部分。这里先判断目前的选择(右键菜单的上下文)是否适合于objectContribution中声明的Object类型,将不适合的 action隐藏,然后尝试调用setActivePart():
if  (ad.getAction()  instanceof  ObjectPluginAction)  {
  
final ObjectPluginAction action = (ObjectPluginAction) ad.getAction();
  ISafeRunnable runnable 
= new ISafeRunnable() {
  
// ...
    public void run() throws Exception {
      action.setActivePart(part);
      action.selectionChanged(selection);
    }

  }
;
  SafeRunner.run(runnable);
}
这里头action的实际类型是org.eclipse.ui.internal.ObjectPluginAction。它的setActivePart()方法里有:
if  (actionDelegate instanceof IObjectActionDelegate  &&  activePart ! =  null) {
  final IObjectActionDelegate objectActionDelegate 
=  (IObjectActionDelegate) actionDelegate;
  final ISafeRunnable runnable 
=   new  ISafeRunnable() {
    
public  void run() throws Exception {
      objectActionDelegate.setActivePart(ObjectPluginAction.this, activePart);
    }
  
//  ...
}
成员actionDelegate就是我们写的代理action类。明显,若它是空,里面的内容,即对我们写的setActivePart()方法的调用就不能进行了,而这就是我们未触发这个action之前它的状态。
 
最后再看看IActionDelegate的selectionChanged()的情况。这个回调函数可以响应view内的 SelectionProvider提供的选择信息,只有当menu item是viewContributor的时候,回调函数才起作用,正常情况下,对view内的选择事件,回调函数都会被调用。
(在上面关于setActivePart()的讨论中,selectionChanged()也被调用了,但这个不算,只有右键菜单弹出的时候,selectionChanged()才会被调用,而我们要它对每次的选择,无论鼠标左键右键,都会有响应。)
首先要在当前的IWorkbrenchPageSite上注册SelectionProvider,以让我们的Provider的选择事件能影 响到我们的Action。一般常见的SelectionProvider就是JFace中的各种Viewer,在ViewPart的实现中调用:
getSite().setSelectionProvider(treeViewer);
这样我们的View就可以响应treeViewer的selection事件了。
事件从Viewer中发出,会一路走到org.eclipse.ui.internal.PluginAction(刚才的ObjectPluginAction是它的一个子类),在它的方法selectionChanged()中我们看到:
if  (delegate  ==   null   &&  isOkToCreateDelegate())  {
  createDelegate();
}
  else   {
  refreshEnablement();
}

似乎若我们的代理还没创建,它会帮我们创建出来,可是且慢,方法isOkToCreateDelegate()中写道:
if  (getStyle()  ==  IAction.AS_DROP_DOWN_MENU  &&   ! WWinPluginPulldown. class .isInstance( this ))  {
  
return true;
}

//  test if the plugin has loaded
String bundleId  =  configElement.getNamespace();
return  BundleUtility.isActive(bundleId);
即,若我们的Action是drop down menu item,那么会被创建,或者Action所属的插件已经加载,那么会被创建。
对于缺省的Action,这些判断不会通过。所以我们的Action仍旧没有创建。而在refreshEnablement()中,有:
if  (delegate  !=   null {
  delegate.selectionChanged(
this, selection);
}

空的delegate被悲惨的忽略了。
 
总之,对于RCP中声明的Action,要小心,我们写的回调函数并不是总能被调用的。在Action真正的被激活之前,我们的代理类(大多数情况下)并不会被主动的创建。
这个问题的解决方法实际已经有了,只要让isOkToCreateDelegate()返回true就可以让代理类被主动创建,经试验,只要声 明时将Action的Style设置成pulldown即可。这样,只要右键菜单一弹出,或者selection事件一发送,代理类就会被创建,回调总能 被调用。

Perspective?

出于对声 明式编程的偏爱,我更喜欢declarative actions。但对于客户的需求,显然用declarative actions并不合适,我们需要在每一个action的回调中判断当前的perspective,然后决定是否让它可用,这是很麻烦的编程,遵从DRY 原则,我们应该对actions直接编程。
为什么会这样?对Eclipse的偏袒让我认为这是客户需求上的问题。Eclipse的declarative actions本身是自洽的系统。一个action是否可用应该只针对它当前最小的上下文,在我们的项目里就是被选择的树节点的类型有关,而整个系统的状 态,比如当前处在哪个perspective中,不应该是action主要考虑的内容。
况且,perspective不应该作为划分任务的依据。Eclipse RCP中对于perspective的定义只是一个“屏幕页面的模板”,它记录一组缺省的view和他们的布局情况,作为方便用户的一种选择。它甚至不在 Eclipse RCP的UI组件的继承结构之内,而只是集合UI组件的一个逻辑概念。真正执行功能的基础UI元件应该是View。在Eclipse中,我们随时可以看到 IDE提示我们切换perspective,但从来不会看到某一个action只能在这个perspective中使用而不能在另一个中使用。比如说启动 了Debug功能后IDE会告诉我们切换到Debug Perspective,但“这个Perspective中的”所有View和所有功能,我们一样可以在Java视图中用,在PD视图中用,在 Resources视图中用,等等。只是Debug Perspective为我们打开了一组缺省的View和保持了一组缺省的布局而已。
为了实现对用户功能的多样化,我们应该实现多种不同的View,在不同的View上实现不同的右键菜单,如果两个View功能很重复,也应该考虑使用模块化的UI组件,而不是根据Perspective来决定同一个View的不同表现。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值