基於 SailingEase WinForm Framework 開發客戶端程序(3:實現菜單/工具欄按鈕的解耦及狀態控制)

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

 

 

本系列文章將詳細闡述客戶端應用程序的設計理念,實現方法。框架

本系列文章以  SailingEase WinForm Framework 爲基礎進行設計並實現,但其中的設計理念及方法,亦適用於任何類型的客戶端應用程序的設計與開發。異步

 

目錄:模塊化

http://www.cnblogs.com/sheng_chao/p/6084144.html 工具

 

SailingEase WinForm Framework性能

 

其實這是從 IDE 項目中提取出來的一個純開發框架,它沒有用戶管理、權限管理之類的現成功能,而是提供純開發角度的開發框架,歸納來講提供瞭如下幾方面的功能:學習

      a.宿主程序(殼)與功能模塊(插件)的加載、調度、通訊等實現;ui

     b.不一樣插件之間在徹底接耦合的基礎上,同步/異步調用、狀態響應等機制的實現;this

     c.插件之間在代碼層面徹底沒有互相引用關係,能夠實如今缺乏任意插件的狀況下啓動應用,即便他們在UI層有交集;spa

     d.支持模塊間的依存關係定義;

     d.事件聚合器,用於在徹底解耦的條件下,發佈及訂閱事件;

     d.宿主程序提供了統一的主菜單及右鍵菜單的註冊/吊銷/狀態控制機制;

     e.宿主程序提供了統一的窗口調度/加載/銷燬功能;

     f.宿主程序提供了統一的日誌記錄、異常捕獲,Web頁面互操做等功能;

     g.基於 GDI+ 自行實現的控件包,提供了高度的可擴展性;

     h.基於zip格式的文件包管理器(基於zip的自定義文件格式,讀取或寫入指定的流);

     i.對http、xml、磁盤io、反射、加解密等操做的加強與封裝; 

     j.其它……

 

第三章節:實現菜單/工具欄按鈕的解耦及狀態控制

 

咱們回顧一下上一章節中的客戶端程序結構圖:

能夠留意到,主菜單主主菜單下面的子菜單,以及工具欄按鈕,是容許模塊在其中註冊新項的,咱們將在本章節中詳細講解這一細節的實現方法。

前文提到,一些入門級的模塊化程序,一般直接將工具欄或菜單的視圖,定義並公開,加載到宿主程序中,這樣作有幾具問題:

1.性能較差,由模塊直接負責相關視圖的切換或隱藏/顯示,對於複雜視圖性能極低;

2.可靠性低,同上,模塊負責操做宿主的視圖,很是容易破壞宿主的應用程序結構,並致使宿主沒法handle其中的異常;

3.沒法解耦菜單/工具欄的程序功能和視圖表現,例如將傳統菜單工具欄表現形式升級爲Office 2016形式,此時必須所有模塊重寫相關視圖加宿主大改;

那麼如何在應用程序的宿主中,向插件提供統一的菜單,工具欄註冊,更新,銷燬機制呢?以及如何作到UI無關的完全解耦合?

這裏的主要思路是:模塊不能直接定義菜單/工具欄的視圖,而是能過其它方式,定義它們的功能和意義,由宿主程序解析並呈現。

 

看兩個例子:

基於 Winform 的插件式應用程序:

 http://www.cnblogs.com/sheng_chao/p/4387249.html

這是一個基於 Winform 的 IDE 程序,與 Visual Stuido 很是相似,這是一個典型的例子, Visual Studio 功能繁多,菜單和工具欄的切換很是頻繁。

主菜單及工具欄根據加載的模塊,以及當前激活的窗體有所不一樣,菜單及工具欄按鈕的狀態則根據當前激活窗體內的數據或行爲的不一樣而有所不一樣。

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

 

基於 WPF 的插件式應用程序:

 http://www.cnblogs.com/sheng_chao/p/4548146.html

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

 

上面的兩個例子,雖然一個是 Winform,一個是 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 環境。

 

在後續的章節中,我將繼續闡述如何基於 SailingEase Winform Framework 進行模塊化的客戶端應用程序設計。

歡迎加我QQ交流探討,共同窗習:279060597,另外我在南京,有南京的朋友嗎?

相關文章
相關標籤/搜索