在插件式應用程序中,實現對菜單,工具欄按鈕的 徹底解耦及狀態控制

 

SailingEase WinForm Framework WinForm開發框架開發手冊:http://docs.shengxunwei.com/Home/Browser/sewinformfw/html

 

 

 

 

以前承諾會對 Winform IDE,WPF 客服程序的開發進行進一步的分解記錄,很抱歉一直沒有太多時間認真梳理。框架

 

本篇博客抽取了這兩個應用程序的一個共通功能的實現方法進行說明,即在插件式應用程序中,對菜單及工具欄的控制。模塊化

 

對於複雜的應用程序開發,咱們可能會將程序的功能進行分解,模塊化,插件化;那麼如何在應用程序的宿主中,向插件提供統一的菜單,工具欄註冊,更新,銷燬機制呢?以及如何作到UI無關的完全解耦合?工具

 

看兩個例子:ui

 

基於 Winform 的插件式應用程序: http://www.cnblogs.com/sheng_chao/p/4387249.htmlthis

這是一個基於 Winform 的 IDE 程序,主菜單及工具欄根據加載的模塊,以及當前激活的窗體有所不一樣,菜單及工具欄按鈕的狀態則根據當前激活窗體內的數據或行爲的不一樣而有所不一樣。spa

圖中黃色背景的工具欄部分爲窗體設計器所特有,相似於在新版的 Word 中選中圖形或表格時出現的特定菜單項目。每當在窗體設計器中進行不一樣的操做時,工具欄中的項目將呈現不一樣的狀態。插件

 

基於 WPF 的插件式應用程序: http://www.cnblogs.com/sheng_chao/p/4548146.html設計

這個是一個基於 WPF 開發的普通桌面應用程序,根據當前加載的模塊不一樣,上方主菜單顯示的項目有所不一樣。這個例子比較簡單,雖然主菜單是根據插件而加載的,可是加載以後不會有狀態變化。日誌

 

 

通常來講,宿主程序在加載插件時,會根據某種預先配置的插件信息(如配置文件),讀取與插件相關的信息進行加載。

過去的許多應用程序,經過將菜單及工具欄的配置經過配置文件來向宿主進行聲明,這種方式的優勢是實現簡單,開發容易,幾乎沒有難度,缺點是幾乎只能以靜態方式對菜單及工具欄進行配置,若是須要在程序運行時動態更新、吊銷菜單或工具欄,按此思路實現起來已不是最優選擇。

第二種方式也是我常常看到的,就是開發人員直接把菜單或工具欄從UI層拋給插件去實現,宿主只提供一個基本UI容器去承載插件所提供的UI對象,好比整個 UserControl。這種方式若是必定要說有什麼優勢,那就是開發實現比較簡單,點則比第一種方式更多,首先宿主程序失去了對插件的絕對控制,插件程序能夠經過提供本身形態萬千的UI,使主程序的相關功能呈現,控制,再也不統一,其次使主程序變得很是脆弱,宿主程序沒法有效的,徹底的 Handle 來自這些UI的異常,也沒法監控,控制這些UI中的方法調用,例如對超時的方法調用顯示等待UI,或強行停止,沒法調度這些方法調用。當宿主程序因升級而修改了菜單和工具欄的呈現形態時,或須要支持換膚功能時,插件提供的UI徹底不受控。此外這種方式可能帶來大量的重複勞動,浪費開發人員生產性,由於大多數的菜單,工具欄項目的呈現,都是類似的,有必定規律的,能夠經過自動化的方式來處理。

第三種方式的思路是由宿主程序提供接口,供插件進行調用,從而使插件可以對菜單及工具欄進行動態控制,這樣作的好處一是不存在方法二中的問題,二是解決了方法一中,靜態加載所不能實現的動態控制。

實現的方式有許多,過去咱們見到過提供一系列方法來供插件調用的狀況,這樣作有一個顯著缺點,就是複雜,會使代碼複雜化,邏輯複雜化。須要提供一系列的註冊,更新,吊銷方法,以及許多不一樣的參數重載以實現相應的功能。當開發中存在新需求時,如對菜單及工具欄項綁定權限 Key,就須要一系列的接口修改或參數修改。

我在上面兩個例子中,將菜單和工具欄資源化,經過一種 相似URI,統一資源標識符的方式 來控制,最大程度的將插件開發的工做量降到最低,最容易,使實習生水平的開發人員,經過10分鐘的講解,就能夠從容掌握。

 

實現效果:

        private void InitializeNavigation() { _navigationService.Register("MainMenu://Session[Text='會話']/Session/"); _navigationService.Register("MainMenu://Session/Session/Contact[Text='聯繫人']", new Action(() => { ContactView.ShowInstance(); })); _navigationService.Register("MainMenu://Setup[Text='設置']/Contact/"); _navigationService.Register("MainMenu://Setup/Contact/CustomerCategory[Text='業務類型',AuthorizeKey='ManageCustomerCategory']", new Action(() => { CustomerCategoryListView.ShowInstance(); })); _navigationService.Register("MainMenu://Setup/Contact/CustomerImportentLevel[Text='重要級別',AuthorizeKey='ManageCustomerImportentLevel']", new Action(() => { CustomerImportentLevelListView.ShowInstance(); })); }

相信稍具經驗的開發人員,無需解釋亦能明白這段代碼的含義。

插件在獲得宿主提供的 INavigationService (_navigationService)接口後,只需調用 Register 方法,傳入 URI 及相關參數,便可實現對菜單或工具欄項目的動態註冊。

 

INavigationService 接口的定義很是簡單:

    public interface INavigationService { void Register(string path); 
        void Register(string path, Action action); void Register(NavigationCodon codon); void Update(string path); }

 

從字面意思便可徹底理解,避免了傳統的大段方法來提供相關的功能,核心就在於參數 path ,統一資源標識符。

 

 

協議部分根據宿所能提供的功能實現既可,如:

MainMenu:主菜單;Toolbar:工具欄:QuickStart:快速啓動工具等等

以 MainMenu 爲例:

路徑路分即指明當前目標菜單的「層級」,在這個例子中,路徑的第一部分 Setup,在上文 Winform 應用的例子中,實現爲頂層菜單,而對於第二個 WPF 例子,採用了 Ribbon 式的菜單,則實現爲 Tab 頁;路徑第二部分的 Contact 實現爲二級菜單,或忽略,在 Ribbon 式菜單中,實現爲 Tab 頁下的 Group;第三部分 CustomerCategory 則指明瞭具體的菜單項目「業務類型」。

路徑的第三部分 CustomerCategory 僅指定了該菜單項的 Name,其它屬性均經過以中括號括起的屬性語法來指定,即:Text='業務類型',AuthorizeKey='ManageCustomerCategory'。

在具體實現中,屬性語法中的可用屬性,通過特別處理,容許框架無關,UI無關,容許動態擴展。對於屬性語法中的可用屬性進行擴展,很是容易。與 INavigationService 自己的實現,是徹底解耦的,無關的。

意味着隨着應用程序開發的深刻,需求的變化,出現新功能須要對應時,只需在特定位置指明新的屬性名及實現其功能便可,與框架,與INavigationService 皆無關。

全部的新屬性對應,甚至是原有屬性的去除,均可以不影響現有任何代碼,新屬性實現不影響原有代碼,而原有代碼中屬性的屬性若是須要取消,取消相關對應便可,INavigationService 在解析時找不到對應的實現,可在記錄日誌後直接忽略,例如1.0版本的宿主支持指定菜單的顏色,到了2.0不支持了,原有在1.0下工做的代碼,徹底不會受影響,僅僅是該指定到了2.0變爲無效,從而實現良好的向下兼容性。

 

INavigationService 還提供了 Update 方法用於更新菜單或工具欄項目的狀態,同時,直接在 path 中使用屬性語法便可,如:

_navigationService.Update("MainMenu://Setup/Contact/CustomerCategory[Enable='False']",

 

此外,INavigationService 接口支持一個更復雜的參數對象 NavigationCodon

    public class NavigationCodon { public NavigationPath Path { get; private set; }

        public Action Action { get; set; } public Func<bool> IsEnableFunc { get; set; } public Func<Visibility> VisibilityFunc { get; set; } public NavigationCodon(string path) { this.Path = new NavigationPath(path); } }

 

可實如今更爲複雜的場景下對菜單及工具欄項目的精細控制,如上文中的 Winform IDE 環境。

經過將菜單及工具欄項目資源化,不但實現了宿主與插件之間的徹底解耦合,也爲插件自身提供了菜單工具欄解耦合的方法,插件在實現本身的業務時,亦無需獲得對菜單及工具欄項目的強引用,經過 INavigationService 便可進行相關操做。

 

在此拋磚引玉,歡迎批評指正。   :)

相關文章
相關標籤/搜索