Eclipse RCP: Declarative Actions

來源:互聯網
上載者:User
在做一個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的不同表現。
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.