天天看點

分享非常漂亮的WPF界面架構源碼及其實作原理

本文将按照以下四點來介紹:

(1)ModernUI簡介;

(2)建構通用界面架構的思路;

(3)基于ModernUI和OSGi.NET的插件化界面架構實作原理及源碼分析;

(4)其它更有趣的東西~~。

1 ModernUI簡介

分享非常漂亮的WPF界面架構源碼及其實作原理

要編寫這樣的WPF界面,我們需要在一個Window上聲明菜單和Tab頁面,下圖是定義菜單的聲明。

分享非常漂亮的WPF界面架構源碼及其實作原理

此外,每一個Tab風格頁面,你也需要手動的為菜單建立這樣的界面元素。

直接用這樣的方式來使用ModernUI,顯然不太适合團隊協作性的并行開發,因為在一個團隊的協作中,不同的人需要完成不同的功能,實作不同頁面,每個人都需要來更改主界面。

我非常希望子產品化的開發方法,因為這可以盡可能的複用現有資産,使程式員可以聚焦在自己關注的業務邏輯上,不需要關心UI的使用。下面,我将來描述基于ModernUI實作的一個通用界面架構,這個界面架構允許程式員在自己的業務子產品中配置需要顯示的界面元素。

2 通用界面架構實作思路

我希望能夠實作這樣的通用界面架構:

(1)程式員可以直接實作需要展現業務邏輯的界面,不需要關注如何使用ModernUI;

(2)程式員可以通過簡單的配置就可以将自己實作的業務邏輯頁面顯示在主界面中;

(3)這個界面架構可以完全複用。

當我看到ModernUI這個界面庫時,我希望将應用程式做成子產品化,每一個子產品能夠:

(1)通過以下配置能夠直接顯示二級菜單。

分享非常漂亮的WPF界面架構源碼及其實作原理

(2)通過以下配置能夠直接顯示三級菜單。

分享非常漂亮的WPF界面架構源碼及其實作原理

這樣做的好處是,開發插件的時候可以不需要關心界面架構插件;團隊在協作開發應用的時候,可以獨立開發并不需要修改主界面;團隊成員的插件可以随時內建到這個主界面;當主界面無法滿足我們的布局時或者使用者需求無法滿足時,可以直接替換主界面架構而不需要修改任何插件代碼。

最終的效果如下,以下界面的幾個菜單及點選菜單顯示的内容由DemoPlugin插件、DemoPlugin2插件來提供。當插件架構加載更多插件時,界面上會出現更多的菜單;反之,當插件被解除安裝或者被停止時,則相應的菜單将消失掉。

分享非常漂亮的WPF界面架構源碼及其實作原理

下面我來介紹如何實作。

3 基于ModernUI和OSGi.NET的插件化界面架構實作原理及源碼分析

3.1 OSGi.NET插件架構原理簡介

OSGi.NET架構是一個完全通用的.NET插件架構,它支援WPF、WinForm、ASP.NET、ASP.NET MVC 3.0/4.0、控制台等任意.NET應用程式,也就是說,你可以基于該插件架構來快速構架插件化的應用程式。OSGi.NET插件架構提供了插件化支 持、插件擴充和面向服務支援三大功能。

OSGi.NET插件架構啟動時,從插件目錄中搜尋插件,安裝并啟動這些插件,将這些插件組裝在插件架構中;一個插件可以暴露擴充點,允許其它插件在不更改其代碼情況下,擴充該插件的功能;插件間可以通過服務來進行通訊。

在一個插件應用程式中,它首先要擷取一個入口點,這個入口點由一個插件來提供,然後進入這個插件的入口并運作起來。一個提供入口的插件通常是一個主 界面插件,比如上面介紹的這個WPF界面架構。也就是說,插件應用程式啟動起來後,會先運作這個界面架構的主界面。而主界面一般都提供了關于界面元素的擴 展,允許其它插件将菜單、導航和内容頁面注冊到主界面,是以,當主界面運作時,它會将其它插件注冊的界面元素顯示出來。當使用者點選界面元素時,插件架構就 會加載這個插件的頁面,某個插件的頁面在呈現時,則有可能會從資料庫中提取資料展示,這時候,該插件則可能會調用資料通路服務提供的通用資料通路接口。 OSGi.NET提供的三大功能,剛好能夠非常的吻合這樣的系統的啟動形式。當然,OSGi.NET除了提供插件三大支撐功能之外,它還支援插件動态性與 隔離性。動态性,意味着我們可以在運作時來動态安裝、啟動、停止、解除安裝和更新插件,而隔離性則意味着每一個插件都擁有自己獨立的目錄,有自己獨立的類型加 載器和類型空間。

基于OSGi.NET插件架構,我們很容易實作插件的動态安裝、遠端管理、自動化部署、自動更新和應用商店。下面,我來描述如何使用OSGi.NET來建構一個WPF插件應用。

3.2 基于OSGi.NET來實作WPF插件應用

利用OSGi.NET來建立一個WPF插件應用非常的簡單。隻需要實作:(1)建立一個插件主程式,定義插件目錄;(2)在主程式中利用 BootStrapper實作OSGi.NET核心更新檢測與自動更新;(3)啟動插件架構;(4)利用PageFlowService擷取主界面,然後 運作主界面。下面我們看一下插件主程式。(注:如果你安裝了OSGi.NET架構,可以直接使用項目模闆來建立WPF主程式項目。)

分享非常漂亮的WPF界面架構源碼及其實作原理

在這個主程式,我們在項目的屬性将輸出路徑改為bin,并在bin目錄下建立一個Plugins目錄,然後将OSGi.NET四個标準插件拷貝到 Plugins目錄,它們分别用于:(1)插件遠端管理,即RemotingManagement和WebServiceWrapperService, 支援遠端管理控制台調試用;(2)插件管理服務,即UIShell.BundleManagementService,支援對本地插件管理和插件倉庫通路 與下載下傳;(3)頁面流服務,即UIShell.PageFlowService,用于擷取主界面。

下面我們來看一下App.xaml.cs源碼,在這裡實作了插件加載、啟動和進入主界面的功能。

分享非常漂亮的WPF界面架構源碼及其實作原理
分享非常漂亮的WPF界面架構源碼及其實作原理

上述代碼非常簡單,我将介紹一下每一個函數的功能。

(1)構造函數:調用UpdateCore和StartBundleRuntime;

(2)UpdateCore:調用BootStrapper程式集的CoreFileUpdater來實作核心檔案更新;

(3)StartBundleRuntime:建立一個BundleRuntime,即插件架構,BundleRuntime預設構造函數指定的插件目錄為Plugins;啟動BundleRuntime,即啟動插件架構;挂載Startup和Exit事件;

(4)在App_Startup事件處理函數中,從插件架構擷取PageFlowService服務,利用該服務擷取主界面,然後建立該界面執行個體,并運作;

(5)在App_Exit事件處理函數中,終止插件架構,釋放資源。

3.3 基于ModernUI實作通用界面插件架構

我在第2節描述了通用界面架構的思路。這個界面架構将基于OSGi.NET插件架構三大功能之一——插件擴充來實作。我将按照以下順序來描述實作。

3.3.1 OSGi.NET插件擴充原理

下圖是OSGi.NET插件擴充原理,在這裡,需要暴露擴充點的插件暴露一個ExtensionPoint,提供擴充的插件則聲明一個Extension(XML格式),如下所示。暴露擴充點的插件通過OSGi.NET架構擷取所有Extension,然後對其進行處理。

分享非常漂亮的WPF界面架構源碼及其實作原理

依據第2節描述,通用界面架構插件需要暴露擴充點和處理擴充。暴露擴充點意味着它需要定義界面擴充的格式。下面我來介紹擴充格式的XML定義。

3.3.2 界面擴充XML定義

根據界面架構要實作的功能,我們定義的擴充格式,如下所示。擴充點的名稱為UIShell.WpfShellPlugin.LinkGroups。 通過LinkGroup來定義一級菜單,通過Link來定義葉子節點菜單,通過TabLink來定義三級菜單的Tab布局方式。

分享非常漂亮的WPF界面架構源碼及其實作原理
分享非常漂亮的WPF界面架構源碼及其實作原理

界面架構插件需要做的就是擷取這樣的XML定義,并且自動在界面上将元素建立出來并自動加載插件提供的頁面。下面我來介紹界面架構如何實作。

3.3.3 界面架構的實作

界面架構基于ModernUI來實作,它需要完成:(1)為Extension建立擴充模型;(2)擷取所有擴充模型對象,并在主界面建立界面元素;(3)監聽擴充變更事件,動态變更界面元素。

首先,我們來看看擴充模型的建構。在這裡,定義了LinkGroupData、TabLinkData、LinkData分别對應于擴充的XML的元素。

分享非常漂亮的WPF界面架構源碼及其實作原理

這裡的ShellExtensionPointHandler對象則用于同OSGi.NET架構擴充擴充資訊,并将其轉換成擴充對象模型,然後存儲 在LinkGroups屬性中。LinkGroups為ObservableCollection,當添加或者删除LinkGroup時會抛出 Add/Remov事件。下面來看一下這個類的代碼。

分享非常漂亮的WPF界面架構源碼及其實作原理
分享非常漂亮的WPF界面架構源碼及其實作原理

這個類有以下幾個方法:

(1)InitExtensions:即從OSGi.NET架構擷取已經注冊的擴充資訊,将其轉換成LinkGroupData執行個體,并儲存;

(2)Context_ExtensionChanged事件處理函數:即當Extension被添加或者删除時的處理函數,這在插件安裝和解除安裝時 發生,我們需要将建立的Extension轉換成LinkGroupData執行個體儲存起來,需要已删除的Extension對應的 LinkGroupData執行個體移除掉。

那接下來我們來看一下主界面如何根據擴紮模型來建立或者删除界面元素。首先,你可以看到,這個主界面是空的沒有預先定義任何的界面元素。

分享非常漂亮的WPF界面架構源碼及其實作原理

那你一定猜到了,這個界面肯定是通過代碼來動态建立界面元素,我們來看看代碼先。

分享非常漂亮的WPF界面架構源碼及其實作原理
分享非常漂亮的WPF界面架構源碼及其實作原理

上面的代碼也很簡單,邏輯很清晰,我來說明一下各個方法的用處:

(1)InitializeLinkGroupsForExtensions:擷取擴充模型對象,并将對象轉換成界面元素LinkGroup,然後監聽擴充模型變更事件;

(2)LinkGroups_CollectionChanged:擴充模型變更事件,當有擴充對象添加時,需要添加新的界面元素;反之,則需要移除界面元素;

(3)CreateLinkGroupForData:為擴充模型建立界面元素LinkGroup;

(4)RemoveLinkGroupForData:當擴充模型被删除時,需要将對應的界面元素删除掉。

為了支援插件化,還需要為ModernUI做一個變更,下面我将來介紹。

3.4 ModernUI插件化支撐所做的變更

為了支援插件化,我需要對ModernUI的ContentLoader進行擴充,使其支援直接從插件加載内容頁面。詳細檢視以下代碼。

分享非常漂亮的WPF界面架構源碼及其實作原理
分享非常漂亮的WPF界面架構源碼及其實作原理

這內建了預設的加載行為,同時支援:(1)以“[BundleSymbolicName]@[PageClassName]”方式支援内容加載;(2)支援WPF傳統資源加載方式;(3)支援參數化。

另外,為了實作三級菜單,我定義了一個ContentPlaceHolder,它用于擷取第三級的菜單,并建立内容,代碼如下。

分享非常漂亮的WPF界面架構源碼及其實作原理
分享非常漂亮的WPF界面架構源碼及其實作原理

它利用傳遞的參數可以擷取對應的三級菜單的擴充模型,然後建立對應的界面元素。

到此,我們已經成功實作了整個插件化的界面架構了,文章有點長,能堅持看到這的基本屬于勇士了~~,接下來還想用一點點篇幅示範一下界面架構動态性。

4 動态性示範

OSGi.NET動态性支援允許我們在程式運作中來安裝、啟動、停止、解除安裝和更新插件,請看下圖。當你運作下載下傳的程式時,最開始會展示以下菜單,其 中“示範11、示範12”菜單由DemoPlugin插件注冊,“示範3”由DemoPlugin2插件提供,此時,你運作一下遠端管理控制台,輸入 list指令後,可以發現這兩個插件都是Active狀态。

分享非常漂亮的WPF界面架構源碼及其實作原理

下面我們輸入“stop 2”指令,将DemoPlugin插件停止,如下圖所示,此時你可以發現DemoPlugin注冊的菜單已經動态的從主界面中被移除掉了。

分享非常漂亮的WPF界面架構源碼及其實作原理

同樣,你還可以繼續嘗試Start、Stop、Install、Uninstall等指令來動态更改插件狀态,進而影響應用程式的行為。

當然,你也可以通過“插件管理”來實作對核心安裝的插件的狀态變更,如下所示。

分享非常漂亮的WPF界面架構源碼及其實作原理

再進一步,你可以直接通路插件倉庫來安裝更多的插件。你可以在源碼中檢視到如何實作插件管理和插件倉庫通路及下載下傳安裝插件的代碼。

分享非常漂亮的WPF界面架構源碼及其實作原理

好了~,非常感謝這麼耐心看完這篇文章。該附上源碼了~。

分享非常漂亮的WPF界面架構源碼及其實作原理