天天看點

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

1 源碼下載下傳

2 OSGi.NET插件應用架構概述

基于OSGi.NET插件架構的應用由以下三個部分構成:

(1)主程式:針對特定應用環境(WPF、Web、WinForm等應用環境),加載啟動插件,擷取插件入口,運作入口程式。

(2)插件:提供應用功能,實作對其它插件功能擴充并暴露功能擴充點。

(3)插件架構:與特定應用環境無關,實作插件的加載、啟動、停止、更新和解除安裝,實作插件功能組合與擴充。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

3 漂亮界面架構原理概述

WPF漂亮界面架構最終展示效果如下圖所示。主界面中間區域的左邊是導航欄,右邊是顯示區域,點選導航欄的導航節點後,在内容區域動态顯示其内容。此外,還提供了标題欄、狀态欄、系統菜單、系統設定等預設功能。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

該界面,從功能上看,它由界面架構插件、示範插件、權限管理插件、插件中心插件以及通用功能插件構成,如下所示。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

這些插件的功能組合關系如下所示,"應用 = 界面架構插件 + 功能插件(示範/權限管理/插件中心插件)擴充"。界面架構定義了系統主界面風格、可擴充的屬性導航欄、可擴充的内容區域等元素構成。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

上述的權限管理插件除了提供角色管理/使用者管理功能,它還定義了一個登入窗體。主程式exe檔案在執行時,首先建立并啟動OSGi.NET插件架構,然後通過服務總線擷取權限管理插件注冊的登入窗體,并顯示。此時,程式執行的控制權則完全交由插件。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

在權限管理插件的登入界面,登入成功之後,它會顯示界面架構插件定義的MainWindow主界面。該主界面則開始來組合插件的功能。下面,我們來看看插件實作的細節。

4 漂亮界面架構實作

4.1 主程式

主程式主要實作:(1)建立啟動插件架構;(2)擷取入口,并進入入口程式。下面我們來看看這個WPF主程式的入口。

在App.xaml.cs中定義了一個函數StartBundleRuntime,如下所示。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

<code>private</code> <code>void</code> <code>StartBundleRuntime()</code>

<code>{</code>

<code>    </code><code>……</code>

<code>    </code><code>// 建立BundleRuntime</code>

<code>    </code><code>var</code> <code>bundleRuntime = </code><code>new</code> <code>BundleRuntime();</code>

<code>    </code><code>// 不啟動多版本支援</code>

<code>    </code><code>bundleRuntime.EnableAssemblyMultipleVersions = </code><code>false</code><code>;</code>

<code>    </code><code>// 監聽插件狀态變化,更新進度條</code>

<code>    </code><code>bundleRuntime.Framework.EventManager.AddBundleEventListener(BundleStateChangedHandler, </code><code>true</code><code>);</code>

<code>    </code><code>// 監聽架構狀态變化</code>

<code>    </code><code>bundleRuntime.Framework.EventManager.AddFrameworkEventListener(FrameworkStateChangedHandler);</code>

<code>    </code><code>// 将Application執行個體添加到全局服務,與插件進行共享</code>

<code>    </code><code>bundleRuntime.AddService&lt;Application&gt;(</code><code>this</code><code>);</code>

<code>    </code><code>// 啟動插件架構</code>

<code>    </code><code>bundleRuntime.Start();</code>

<code>    </code><code>// 移除事件監聽</code>

<code>    </code><code>bundleRuntime.Framework.EventManager.RemoveBundleEventListener(BundleStateChangedHandler, </code><code>true</code><code>);</code>

<code>    </code><code>bundleRuntime.Framework.EventManager.RemoveFrameworkEventListener(FrameworkStateChangedHandler);</code>

<code>    </code><code>Startup += App_Startup;</code>

<code>    </code><code>Exit += App_Exit;</code>

<code>    </code><code>_bundleRuntime = bundleRuntime;</code>

<code>}</code>

在主程式中,它使用以下代碼來擷取入口,這個入口是一個LoginWindow。

<code>private</code> <code>void</code> <code>App_Startup(</code><code>object</code> <code>sender, StartupEventArgs e)</code>

<code>    </code><code>// 擷取loginWindow執行個體,并顯示該視窗</code>

<code>    </code><code>var</code> <code>loginWindow = bundleRuntime.GetFirstOrDefaultService&lt;Window&gt;();</code>

<code>    </code><code>loginWindow.Loaded += (sender2, e2) =&gt;</code>

<code>    </code><code>{</code>

<code>        </code><code>loginWindow.Activate();</code>

<code>    </code><code>};</code>

<code>    </code><code>loginWindow.Show();</code>

4.2 主程式與插件的通訊

OSGi.NET插件架構提供了一個簡單的方式來實作主程式與插件間的通訊,即服務。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

主程式可以通過插件架構BundleRuntime來注冊和擷取服務,插件可以通過插件激活器的上下文來注冊和擷取服務、或者使用BundleRuntime.Instance這個單例來注冊與擷取服務。也就是說,主程式的BundleRuntime、插件的上下文IBundleContext都是對應相同的服務總線。

服務在這裡表述為:服務 = 接口/基類 + 實作類。比如ISayHelloService接口、SayHelloServiceBase基類、SayHelloService實作類。我們可以注冊服務為:

或者

那麼擷取服務的方式就是:

4.2.1主程式擷取插件注冊的服務

在該架構,主程式需要擷取權限管理插件注冊的登入窗體,然後運作,接着将系統控制權轉交給插件。這時候,主程式通過以下代碼來擷取服務。

(1)建立啟動插件架構

(2)擷取服務

權限管理插件在Activator類中,通過以下代碼将LoginWindow注冊到服務總線。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享
【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

這裡,需要注意的是:主程式隻能等插件架構啟動起來後,才能夠擷取插件注冊的服務。

4.2.2插件擷取主程式注冊的服務

主程式可以為插件注冊全局的服務,這樣所有插件在啟動的時候,就可以直接來通路。主程式注冊全局服務的代碼如下:

注意:主程式在BundleRuntime.Start方法調用前注冊的服務,插件在啟動時即可擷取。

這時候,插件可以在激活器中直接擷取到該服務了。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享
【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

4.2.3 服務接口

在4.2.1小節中,主程式和權限管理插件在處理服務時,使用Window這個類作為服務的契約。這個服務契約是在.NET Framework中直接定義的,是以主程式和插件都可以通路到。如果我們新定義的服務SayHelloService(ISayHelloService接口、SayHelloService服務實作類),那麼這時候主程式和插件都需要通過接口ISayHelloService來擷取服務,這時候建議将ISayHelloService接口定義到一個外部的程式集,主程式可以引用它,插件也可以依賴它。

4.3 權限管理的登入窗體

基于4.2,我們發現通過服務可以實作主程式和插件之間的通訊。當主程式擷取到權限管理注冊的登入窗體執行個體,便擷取該窗體并展現它,此後應用系統便交由插件來控制了。

在權限管理插件的登入窗體,它由LoginUserControl.xaml來實作,在該頁面的背景代碼的登入處理函數中,一旦登入成功,它将建立一個主窗體MainWindow,并且顯示該窗體,如下圖所示。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

在這裡,權限管理插件建立了主窗體MainWindow類,這個類實際上是由界面架構插件定義的主窗體。是以,該插件依賴了界面架構插件,并添加了對UIShell.WpfShellPlugin程式集的引用。如下所示。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

通過上述的工作,登入窗體在登入成功之後,就可以顯示界面架構的主窗體了。

4.4 界面架構插件

應用系統由界面架構插件、服務插件和功能插件構成,它們的組合關系如下所示。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

從界面功能上來講,系統由主界面架構、插件中心插件、權限管理插件、示範插件組成,在其背後還有一些非界面功能插件,比如資料庫通路等。

界面架構插件提供了一個可擴充、可組合的界面功能展示。界面架構插件暴露了一個名為UIShell.NavigationService的擴充點,權限管理插件、插件中心插件、其它插件則定義了針對該擴充點的擴充。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

界面架構對應的擴充格式如下所示。該格式由名為Node的XML節點組成,Node節點可以嵌套包含子節點。

<code>&lt;</code><code>Extension</code> <code>Point="UIShell.NavigationService"&gt;</code>

<code>  </code><code>&lt;</code><code>Node</code> <code>Id="2E3614E0-388D-46E4-88A8-42E7CB3B421F" Name="權限管理"</code>

<code>        </code><code>Icon="/UIShell.RbacManagementPlugin;component/Assets/Permission.png"</code>

<code>        </code><code>Order="490"&gt;</code>

<code>    </code><code>&lt;</code><code>Node</code> <code>Name="角色管理" Permission="RoleManagementPermission"</code>

<code>          </code><code>Value="UIShell.RbacManagementPlugin.RolePermissionUserControl"</code>

<code>          </code><code>Icon="/UIShell.RbacManagementPlugin;component/Assets/Role.png" Order="1" /&gt;</code>

<code>    </code><code>&lt;</code><code>Node</code> <code>Name="使用者管理" Permission="UserManagementPermission"</code>

<code>          </code><code>Value="UIShell.RbacManagementPlugin.UserPermissionUserControl"</code>

<code>          </code><code>Icon="/UIShell.RbacManagementPlugin;component/Assets/User2.png" Order="2" /&gt;</code>

<code>  </code><code>&lt;/</code><code>Node</code><code>&gt;</code>

<code>&lt;/</code><code>Extension</code><code>&gt;</code>

當界面架構插件沒有加載任何擴充時,界面是空白的。左邊導航欄用于加載插件定義的導航菜單,右邊用于加載插件的顯示内容。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

那麼插件中心插件就是由對界面架構插件的擴充及如下功能構成,如下所示。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

插件中心插件對界面架構插件的界面擴充是通過如下的Manifest.xml來定義的。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

同理,權限管理插件也是對界面架構插件定義了擴充并實作了如下功能。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

權限管理插件對界面架構的擴充定義在Manifest.xml中實作,如下所示。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

課程管理這個示例插件也是如此。

4.4.1 導航服務

插件對界面架構的擴充的XML由導航服務來進行解析。通俗的講,該服務實作的是将以下XML節點變更NavigationNode對象。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享
【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

NavigationNode對象如下圖所示,它包含子對象。該對象對應于XML節點。我們可以通過INavigationService來擷取這些對象集合。INavigationService會預設從名字為"UIShell.NavigationService"的擴充點來建立對象。如果我們使用了類似的導航擴充定義,但使用了不同的擴充點,可以使用INavigationServiceFactory來建立指定擴充點的導航服務。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

導航服務還隐藏了針對擴充變更事件的處理。該服務暴露了NavigationChanged事件來通知導航節點變更。

4.4.2 界面架構擴充實作

界面架構首先需要實作一個空的布局,其内容區域為樹和空白顯示區域。樹使用TreeView,空白顯示區域的父控件是DockPanel。那麼,該架構實作的核心就是将NavigationNode的集合轉換成TreeViewNode集合,當點選TreeViewNode時,能夠将其對應的使用者控件加載。

界面架構的XAML如下所示。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享
【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

從這些XAML片段,你可以看到,LayoutDockPanel這個名字的控件時用于放置動态加載的插件的控件,加載時機是在NavigationTreeView的SelectedItemChanged事件。另外,該界面架構還實作了SideBarDockPanel,用于支援從側面動态滑出一個側邊框。

下面我們看看界面架構針對擴充的處理。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

接着我們看看ResetNavigation函數的實作。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

其實作的核心就是InitializeNavigationTreeView。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

該函數就是根據NavigationNode集合,遞歸建立TreeViewItem。下面我們來看看點選樹形導航節點時,如何動态加載顯示插件的控件,其核心代碼如下。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

從插件動态加載類型時,我們使用的是node.Bundle.LoadClass,即擷取擴充注冊的插件對象,調用該對象的LoadClass方法來加載使用者控件,然後将使用者控件顯示在LayoutDockPanel控件。

不過,目前界面架構還處理一些其它的功能:

(1)目前導航節點的側邊欄,即當切換菜單時,會自動打開/關閉與其關聯的側邊欄;

(2)緩存與關閉,即加載使用者控件後,會直接緩存,在切換時,會将前一個控件隐藏,接着顯示目前控件;隻有關閉後,使用者控件才從父控件移除掉;

(3)關閉内容區域與導航節點選擇的同步,也就是說,關閉目前内容後,會預設顯示前一個頁面,此時,導航節點的選擇也必須同步切換;

(4)相關對象的關系存儲。

4.5 插件

下面我将從以下幾個方面來談一下開發插件過程中,需要處理的一些問題。

4.5.1 插件引用了第三方程式集

在主界面架構中,我們依靠第三方控件庫"ModernUI"來實作界面,并對"ModernUI"做深入的定制。在界面架構插件引用該控件時,首先,我們需要将該插件添加到Manifest.xml作為本地程式集,即界面架構插件在運作時需要與該程式集一起才能夠正常運作。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

接着,可以直接從bin目錄來引用該程式集或者添加ModernUI源碼項目的程式集引用。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

這時候,在界面架構插件中,就可以來直接使用ModernUI程式集的類型了。如下示例。

4.5.2 一個程式集如何讓所有插件都直接使用

在這個WPF應用程式,每一個插件在開發界面時大部分使用了MVVM架構,它依賴于MVVMLite這個庫。為了能夠讓插件直接使用,并且不需要将其添加到本地程式集的情況下來使用。我們可以在主程式裡面直接添加對MVVMLite程式集的依賴,編譯後,每一個插件可以直接來引用主程式輸出目錄下的MVVMLite程式集。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

你可以發現,MVVMLite程式集所在的位置。如果是Web應用的話,這些程式集所在目錄是bin目錄。這樣的程式集在OSGi.NET架構中成為全局程式集,預設開啟支援該功能。你可以通過設定BundleRuntime.EnableGlobalAssemblyFeature屬性開啟或者關閉該功能。

全局程式集有以下特點:(1)如果插件包含了另一個程式集,和該程式集名稱一樣,則會被替換掉;(2)全局程式集不支援多版本。

4.5.3 插件引用了另一個插件的程式集

在該界面架構中,所有UI插件都是基于ModernUI控件庫來實作。該控件庫在界面架構中包含。是以,我們的功能插件需要引用界面架構插件的ModernUI控件庫。

首先,在界面架構,需要将該程式集定義成共享。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

接着,在功能插件中,需要添加對界面架構的依賴。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

最後,插件就可以直接通過引用,來添加對該程式集的引用,并在代碼中來調用了。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

4.5.4 插件間的通訊實作

插件間的通訊,有兩種方式,第一種是一個插件直接使用另一個插件的程式集的類,如4.5.3的方式;第二種是松耦合的方式,即使用服務。

比如,在示範插件,我們引用了配置服務。配置服務是在配置服務插件來建立的,該服務定義如下所示。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

該插件通過Activator來注冊服務執行個體,如下所示。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享
【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

示範插件依賴于IConfigurationService接口所在的程式集,通過該接口來擷取服務,如下所示。

【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享
【源碼分享】WPF漂亮界面架構實作原理分析及源碼分享

接着,在示範插件就可以通過以下方式來存儲或者擷取配置了。

4.5.5 如何從插件動态的加載類型

從插件加載類型的方式通過插件對象來實作。插件對象由OSGi.NET架構建立,可以通過插件激活器的IBundleContext.Bundle屬性擷取。

5 關于架構的藝術

架構的藝術并不在于技術本身,而是在于能夠幫助團隊更有效率的進行産品開發。為了提高産品開發效率,架構必須能夠提供:

(1)統一的開發模闆:通過模闆來規範團隊成員的編碼規則與規範功能子產品的架構,減少軟體開發的學習成本。比如,我們制作的示範插件模闆,在這個模闆基礎上做功能開發,是不需要你掌握多少關于架構本身的技術,而是專注于業務實作及通用功能的調用;此外,該模闆規範了MVVM架構分層,統一了架構思想。

(2)一緻的使用者體驗:通過架構為客戶定義了一緻的界面風格,這使我們的軟體看上起更加的專業。

(3)良好的分工協作:通過架構,團隊成員可以專注于不同的功能子產品,進行有效率的并行協作。

6 總結

這個教程介紹了漂亮界面架構的架構、實作細節,通過這個教程,你已經能夠掌握使用OSGi.NET架構來開發一個漂亮界面架構了。

本文轉自道法自然部落格園部落格,原文連結:http://www.cnblogs.com/baihmpgy/p/osgi_muinavtree_fx.html,如需轉載請自行聯系原作者