首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

GEF 入門系列(六):添加菜單與工具條

     編輯:Java 相關知識

一旦體會到 GEF 的妙處,便會被其吸引。不僅因用它做出的圖形界面美觀,更因 UI 中最複雜精細的問題,在 GEF 設計中無不被周到考慮並以適當模式解決。了解這些解決方法後,可轉換應用於其他領域設計。去年黃老大在一個 GEF 專案結束後仍持續研究,甚至利用業餘時間開發基於 GEF 的 SWT/JFace 增強軟體包,Eclipse 和 GEF 的魅力可見一斑。相信未來兩年,隨著 RCP/GEF 等技術成熟,Java Standalone 應用程序必將發展,在 B/S 模式難以實現的領域扮演重要角色。

本篇主題是實現菜單功能。因 Eclipse 可擴展設計,在 GEF 應用中添加菜單需多處考慮,故先介紹 Eclipse 菜單相關概念,再通過實例描述如何在 GEF 中添加菜單、工具條和上下文菜單。

Eclipse 插件與菜單基礎

Eclipse 本身是平台,用戶不能直接用它工作,它為提供實際功能的部件提供基礎環境,所有部件通過平台指定方式構造界面和使用資源。這些部件稱為插件,如 JDT、Ant 支持、CVS 客戶端、幫助系統等。從 eclipse.org 下載的 Eclipse 已包含常用插件,無需額外安裝。若需新功能,需下載安裝或在線更新安裝到平台,如 XML 編輯器、Properties 編輯器、J2EE 開發支持等,GEF 開發包也是此類插件。插件一般安裝在 plugins 子目錄,也可用 link 方式安裝在其他位置。

Eclipse 平台優勢在於眾多插件能完美集成同一環境。每個插件可能具有編輯器、視圖、菜單、工具條、文件關聯等複雜元素,讓它們和平共處非易事。Eclipse 提供一系列機制解決此類問題。本文僅簡單講述菜單和工具條部分,更多內容請參考 Eclipse 隨機提供的插件開發幫助文檔。

開發基於 Eclipse 的應用程序即開發一個 Eclipse 插件。每個插件有一個 plugin.xml 定義插件內各種元素,如編輯器、視圖等。視圖中使用菜單和工具條請參考前文,本篇只介紹編輯器情況,因 GEF 應用多基於編輯器。

圖1 Eclipse 平台的幾個組成部分

Retarget Action 概念

Retarget Action 是一種具有語義但無實際功能的 Action,唯一作用是在主菜單條或主工具條上佔據一個項位置。編輯器可將具有實際功能的 Action 映射到某個 Retarget Action,當編輯器被激活時,主菜單/工具條上的該 Retarget Action 就會具有那個 Action 的功能。例如 Eclipse 提供 IWorkbenchActionConstants.COPY 這個 Retarget Action,其文字和圖標預先定義好。假設編輯器需「複製節點到剪貼板」功能,因「複製節點」和「複製」語義相近,可新建 CopyNodeAction(extends Action),然後在適當位置調用:

IActionBars.setGlobalActionHandler(IWorkbenchActionConstants.COPY, copyNodeAction)

當編輯器被激活,Eclipse 會檢查到此映射,讓 COPY 項變為可用狀態,用戶按下時執行 CopyNodeAction 的 run() 方法。引入 Retarget Action 目的在於盡量減少主菜單/工具條重建消耗,並有利於用戶使用上的一致性。在 GEF 應用中,因可能存在多個視圖(如編輯視圖和大綱視圖),每個視圖都應能完成相似操作(如大綱視圖也應能刪除選中節點),故一般操作都應以映射到 Retarget Action 方式建立。

主菜單/主工具條

編輯器沒有自己的菜單欄和工具條,菜單只能加在主菜單裡。因一個編輯器可有多個實例,它們應具有相同菜單和工具條,故在 plugin.xml 定義編輯器時,editor 元素有一個 contributorClass 屬性,其值為實現 IEditorActionBarContributor 介面的類全名,該類可稱「菜單工具條添加器」。在添加器中可向 Eclipse 主菜單/主工具條添加所需項。以本專案為例,需對每個操作支援撤銷/重做,對畫布上每個元素可刪除,對每個節點元素可設置優先級為高、中、低三級。故需添加六個 Retarget Action,以下為 DiagramActionBarContributor 部分代碼:

public class DiagramActionBarContributor extends ActionBarContributor {
    protected void buildActions() {
        addRetargetAction(new UndoRetargetAction());
        addRetargetAction(new RedoRetargetAction());
        addRetargetAction(new DeleteRetargetAction());
        addRetargetAction(new PriorityRetargetAction(IConstants.PRIORITY_HIGH));
        addRetargetAction(new PriorityRetargetAction(IConstants.PRIORITY_MEDIUM));
        addRetargetAction(new PriorityRetargetAction(IConstants.PRIORITY_LOW));
    }
    protected void declareGlobalActionKeys() {}
    public void contributeToToolBar(IToolBarManager toolBarManager) {...}
    public void contributeToMenu(IMenuManager menuManager) {
        IMenuManager mgr = new MenuManager("&Node", "Node");
        menuManager.insertAfter(IWorkbenchActionConstants.M_EDIT, mgr);
        mgr.add(getAction(IConstants.ACTION_MARK_PRIORITY_HIGH));
        mgr.add(getAction(IConstants.ACTION_MARK_PRIORITY_MEDIUM));
        mgr.add(getAction(IConstants.ACTION_MARK_PRIORITY_LOW));
    }
}

DiagramActionBarContributor 繼承 GEF 提供的 ActionBarContributor(實現 IEditorActionBarContributor 的抽象類)。buildActions() 創建要添加到主菜單/工具條的 Retarget Actions 並註冊到專門註冊表;contributeToMenu() 將這些 Retarget Actions 實際添加到主菜單欄,用 IMenuManager.insertAfter() 讓新菜單出現在指定系統菜單後面;contributeToToolBar() 則是添加到主工具條的代碼。

圖2 添加到主菜單條和主工具條上的 Action

GEF 在 ActionBarContributor 維護 retargetActions 和 globalActionKeys 兩個列表,後者是 Retarget Actions 的 ID 列表。addRetargetAction() 會把一個 Retarget Action 同時加到二者中。對於已有的 Retarget Actions(如撤銷/重做/刪除),應在 declareGlobalActionKeys() 中調用 addGlobalActionKey() 聲明。編輯器被激活時,與 globalActionKeys 中 ID 相同的(具有實際功能的)Action 將被關聯到該 ID 對應的 Retarget Action,故無需顯式調用 setGlobalActionHandler(),只需保證 ID 相同即可實現映射。

GEF 內建了撤銷/重做/刪除三個操作的 Retarget Action(因太常用),ID 分別為 IWorkbenchActionConstants.UNDO、REDO 和 DELETE。設置優先級這個 Action 沒有語義相近的現成 Retarget Action,故自行定義 PriorityRetargetAction(未經國際化處理):

public class PriorityRetargetAction extends LabelRetargetAction {
    public PriorityRetargetAction(int priority) {
        super(null, null);
        switch (priority) {
            case IConstants.PRIORITY_HIGH:
                setId(IConstants.ACTION_MARK_PRIORITY_HIGH);
                setText("High Priority");
                break;
            case IConstants.PRIORITY_MEDIUM:
                setId(IConstants.ACTION_MARK_PRIORITY_MEDIUM);
                setText("Medium Priority");
                break;
            case IConstants.PRIORITY_LOW:
                setId(IConstants.ACTION_MARK_PRIORITY_LOW);
                setText("Low Priority");
                break;
            default:
                break;
        }
    }
}

編輯器中創建實際功能 Action

在編輯器(CbmEditor)的 createActions() 建立具有實際功能的 Actions,它們應為 SelectionAction(GEF 提供)子類,因需得到當前選中節點。以下為 PriorityAction 代碼,編輯器 createActions() 方法:

protected void createActions() {
    super.createActions();
    // 高優先級
    IAction action = new PriorityAction(this, IConstants.PRIORITY_HIGH);
    action.setId(IConstants.ACTION_MARK_PRIORITY_HIGH);
    getActionRegistry().registerAction(action);
    getSelectionActions().add(action.getId());
    // 中等優先級
    action = new PriorityAction(this, IConstants.PRIORITY_MEDIUM);
    action.setId(IConstants.ACTION_MARK_PRIORITY_MEDIUM);
    getActionRegistry().registerAction(action);
    getSelectionActions().add(action.getId());
    // 低優先級
    action = new PriorityAction(this, IConstants.PRIORITY_LOW);
    action.setId(IConstants.ACTION_MARK_PRIORITY_LOW);
    getActionRegistry().registerAction(action);
    getSelectionActions().add(action.getId());
}

注意:每個 Action 的 ID 必須與前面創建的 Retarget Action ID 對應,否則無法映射到主菜單/工具條。這裡只創建了設置優先級的三個 Action,撤銷/重做/刪除的 Action GEF 在父類 GraphicalEditor 已創建(包括全選、保存、列印等),只要別忘記調用 super.createActions() 即可。

GEF 提供的 UNDO/REDO/DELETE 等 Action 會根據當前選擇的 editpart 自動判斷是否可用,自定義 Action 則需在 calculateEnabled() 計算。為實現撤銷/重做,Action 執行時通常建立 Command 加入 CommandStack,再執行 Command,而非直接在 run() 寫執行代碼。以下為 PriorityAction 部分代碼(繼承 SelectionAction):

public void run() {
    execute(createCommand());
}
private Command createCommand() {
    List objects = getSelectedObjects();
    if (objects.isEmpty()) return null;
    for (Iterator iter = objects.iterator(); iter.hasNext();) {
        Object obj = iter.next();
        if ((!(obj instanceof NodePart)) && (!(obj instanceof NodeTreeEditPart)))
            return null;
    }
    CompoundCommand compoundCmd = new CompoundCommand(GEFMessages.DeleteAction_ActionDeleteCommandName);
    for (int i = 0; i < objects.size(); i++) {
        EditPart object = (EditPart) objects.get(i);
        ChangePriorityCommand cmd = new ChangePriorityCommand();
        cmd.setNode((Node) object.getModel());
        cmd.setNewPriority(priority);
        compoundCmd.add(cmd);
    }
    return compoundCmd;
}
protected boolean calculateEnabled() {
    Command cmd = createCommand();
    if (cmd == null) return false;
    return cmd.canExecute();
}

因允許用戶一次對多個選中節點設置優先級,故創建多個 Command 並加入 CompoundCommand,好處是撤銷/重做時也可一次性完成。

上下文菜單

在 GEF 中實現右鍵彈出的上下文菜單很方便,只要寫一個繼承 org.eclipse.gef.ContextMenuProvider 的自定義類,在 buildContextMenu() 編寫添加菜單項代碼,然後在編輯器調用 GraphicalViewer.setContextMenu()。GEF 預定義一些菜單組用於區分不同用途的菜單項,每組在外觀上表現為一條分隔線,如 UNDO 組、COPY 組、PRINT 組等。若菜單項不適合放在任何組中,可放在 OTHERS 組,菜單項很多時也可定義新組分類。

圖3 上下文菜單

假設要實現上圖所示上下文菜單,並已在 ActionRegistry 創建這些 Action(在 Editor createActions() 完成),ContextMenuProvider 應如下寫:

public class CbmEditorContextMenuProvider extends ContextMenuProvider {
    private ActionRegistry actionRegistry;
    public CbmEditorContextMenuProvider(EditPartViewer viewer, ActionRegistry registry) {
        super(viewer);
        actionRegistry = registry;
    }
    public void buildContextMenu(IMenuManager menu) {
        // Add standard action groups to the menu
        GEFActionConstants.addStandardActionGroups(menu);
        // Add actions to the menu
        menu.appendToGroup(GEFActionConstants.GROUP_UNDO, getAction(ActionFactory.UNDO.getId()));
        menu.appendToGroup(GEFActionConstants.GROUP_UNDO, getAction(ActionFactory.REDO.getId()));
        menu.appendToGroup(GEFActionConstants.GROUP_EDIT, getAction(ActionFactory.DELETE.getId()));
        menu.appendToGroup(GEFActionConstants.GROUP_REST, getAction(IConstants.ACTION_MARK_PRIORITY_HIGH));
        menu.appendToGroup(GEFActionConstants.GROUP_REST, getAction(IConstants.ACTION_MARK_PRIORITY_MEDIUM));
        menu.appendToGroup(GEFActionConstants.GROUP_REST, getAction(IConstants.ACTION_MARK_PRIORITY_LOW));
    }
    private IAction getAction(String actionId) {
        return actionRegistry.getAction(actionId);
    }
}

buildContextMenu() 第一句創建預設組,忽略此步後面語句會提示組不存在錯誤。讓編輯器使用此類的代碼一般寫在 configureGraphicalViewer() 中。

因順便介紹了 Eclipse 基本概念,加上代碼較多,本文顯得較長,實際通過查看 GEF 對內建 UNDO/REDO 等實現很容易明白菜單使用方法。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved