日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
WPF漂亮界面框架實(shí)現(xiàn)原理分析及源碼分享

1 源碼下載

直接放出源碼地址,為了編譯源碼,需要下載安裝OSGi.NET插件框架安裝包:http://www.iopenworks.com/。

【1】框架安裝包:MuiTreeNavVsPackage.zip(使用方法見上一篇文章:分享一個漂亮WPF界面框架創(chuàng)作過程及其源碼)。

【2】框架源代碼:MuiTreeNavSource.zip

注意:運(yùn)行后,默認(rèn)賬戶為admin/admin。記得點(diǎn)擊右邊“推薦一下”,否則登錄會失敗!

2 OSGi.NET插件應(yīng)用架構(gòu)概述

基于OSGi.NET插件框架的應(yīng)用由以下三個部分構(gòu)成:

(1)主程序:針對特定應(yīng)用環(huán)境(WPF、Web、WinForm等應(yīng)用環(huán)境),加載啟動插件,獲取插件入口,運(yùn)行入口程序。

(2)插件:提供應(yīng)用功能,實(shí)現(xiàn)對其它插件功能擴(kuò)展并暴露功能擴(kuò)展點(diǎn)。

(3)插件框架:與特定應(yīng)用環(huán)境無關(guān),實(shí)現(xiàn)插件的加載、啟動、停止、更新和卸載,實(shí)現(xiàn)插件功能組合與擴(kuò)展。

3 漂亮界面框架原理概述

WPF漂亮界面框架最終展示效果如下圖所示。主界面中間區(qū)域的左邊是導(dǎo)航欄,右邊是顯示區(qū)域,點(diǎn)擊導(dǎo)航欄的導(dǎo)航節(jié)點(diǎn)后,在內(nèi)容區(qū)域動態(tài)顯示其內(nèi)容。此外,還提供了標(biāo)題欄、狀態(tài)欄、系統(tǒng)菜單、系統(tǒng)設(shè)置等默認(rèn)功能。

該界面,從功能上看,它由界面框架插件、演示插件、權(quán)限管理插件、插件中心插件以及通用功能插件構(gòu)成,如下所示。

這些插件的功能組合關(guān)系如下所示,"應(yīng)用 = 界面框架插件 + 功能插件(演示/權(quán)限管理/插件中心插件)擴(kuò)展"。界面框架定義了系統(tǒng)主界面風(fēng)格、可擴(kuò)展的屬性導(dǎo)航欄、可擴(kuò)展的內(nèi)容區(qū)域等元素構(gòu)成。

上述的權(quán)限管理插件除了提供角色管理/用戶管理功能,它還定義了一個登錄窗體。主程序exe文件在執(zhí)行時,首先創(chuàng)建并啟動OSGi.NET插件框架,然后通過服務(wù)總線獲取權(quán)限管理插件注冊的登錄窗體,并顯示。此時,程序執(zhí)行的控制權(quán)則完全交由插件。

在權(quán)限管理插件的登錄界面,登錄成功之后,它會顯示界面框架插件定義的MainWindow主界面。該主界面則開始來組合插件的功能。下面,我們來看看插件實(shí)現(xiàn)的細(xì)節(jié)。

#p#

4 漂亮界面框架實(shí)現(xiàn)

4.1 主程序

主程序主要實(shí)現(xiàn):(1)創(chuàng)建啟動插件框架;(2)獲取入口,并進(jìn)入入口程序。下面我們來看看這個WPF主程序的入口。

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

 
 
  1. private void StartBundleRuntime()
  2. {
  3.     ……
  4.     // 創(chuàng)建BundleRuntime
  5.     var bundleRuntime = new BundleRuntime();
  6.     // 不啟動多版本支持
  7.     bundleRuntime.EnableAssemblyMultipleVersions = false;
  8.     // 監(jiān)聽插件狀態(tài)變化,更新進(jìn)度條
  9.     bundleRuntime.Framework.EventManager.AddBundleEventListener(BundleStateChangedHandler, true);
  10.     // 監(jiān)聽框架狀態(tài)變化
  11.     bundleRuntime.Framework.EventManager.AddFrameworkEventListener(FrameworkStateChangedHandler);
  12.     // 將Application實(shí)例添加到全局服務(wù),與插件進(jìn)行共享
  13.     bundleRuntime.AddService(this);
  14.     // 啟動插件框架
  15.     bundleRuntime.Start();
  16.     // 移除事件監(jiān)聽
  17.     bundleRuntime.Framework.EventManager.RemoveBundleEventListener(BundleStateChangedHandler, true);
  18.     bundleRuntime.Framework.EventManager.RemoveFrameworkEventListener(FrameworkStateChangedHandler);
  19.  
  20.     Startup += App_Startup;
  21.     Exit += App_Exit;
  22.     _bundleRuntime = bundleRuntime;
  23. }

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

 
 
  1. private void App_Startup(object sender, StartupEventArgs e)
  2. {
  3.     ……
  4.     // 獲取loginWindow實(shí)例,并顯示該窗口
  5.     var loginWindow = bundleRuntime.GetFirstOrDefaultService();
  6.     loginWindow.Loaded += (sender2, e2) =>
  7.     {
  8.         loginWindow.Activate();
  9.     };
  10.     loginWindow.Show();
  11. }

4.2 主程序與插件的通訊

OSGi.NET插件框架提供了一個簡單的方式來實(shí)現(xiàn)主程序與插件間的通訊,即服務(wù)。

主程序可以通過插件框架BundleRuntime來注冊和獲取服務(wù),插件可以通過插件激活器的上下文來注冊和獲取服務(wù)、或者使用BundleRuntime.Instance這個單例來注冊與獲取服務(wù)。也就是說,主程序的BundleRuntime、插件的上下文IBundleContext都是對應(yīng)相同的服務(wù)總線。

服務(wù)在這里表述為:服務(wù) = 接口/基類 + 實(shí)現(xiàn)類。比如ISayHelloService接口、SayHelloServiceBase基類、SayHelloService實(shí)現(xiàn)類。我們可以注冊服務(wù)為:

 
 
  1. AddService(new SayHelloService())

或者

 
 
  1. AddService(new SayHelloService())
 

那么獲取服務(wù)的方式就是:

 
 
  1. Get**Service()

或者

 
 
  1. Get**Service()

4.2.1主程序獲取插件注冊的服務(wù)

在該框架,主程序需要獲取權(quán)限管理插件注冊的登錄窗體,然后運(yùn)行,接著將系統(tǒng)控制權(quán)轉(zhuǎn)交給插件。這時候,主程序通過以下代碼來獲取服務(wù)。

(1)創(chuàng)建啟動插件框架

 
 
  1. var bundleRuntime = new BundleRuntime();
  2. bundleRuntime.Start();

(2)獲取服務(wù)

 
 
  1. var loginWindow = bundleRuntime.GetFirstOrDefaultService();
  2. loginWindow.Show();

權(quán)限管理插件在Activator類中,通過以下代碼將LoginWindow注冊到服務(wù)總線。

 
 
  1. public class Activator : IBundleActivator
  2. {
  3.     public void Start(IBundleContext context)
  4.     {
  5.         context.AddService(new LoginWindow());
  6.     }
  7.     public void Stop(IBundleContext context)
  8.     {
  9.     }
  10. }

這里,需要注意的是:主程序只能等插件框架啟動起來后,才能夠獲取插件注冊的服務(wù)。

4.2.2插件獲取主程序注冊的服務(wù)

主程序可以為插件注冊全局的服務(wù),這樣所有插件在啟動的時候,就可以直接來訪問。主程序注冊全局服務(wù)的代碼如下:

 
 
  1. var bundleRuntime = new BundleRuntime();
  2. bundleRuntime.AddService();
  3. bundleRuntime.Start();

注意:主程序在BundleRuntime.Start方法調(diào)用前注冊的服務(wù),插件在啟動時即可獲取。

這時候,插件可以在激活器中直接獲取到該服務(wù)了。

 
 
  1. public class Activator : IBundleActivator
  2. {
  3.     public void Start(IBundleContext context)
  4.     {
  5.         var sayHelloService = context.GetFirstOrDefaultService();
  6.         sayHelloService.Hell(“Lorry Chen”);
  7.     }
  8.     public void Stop(IBundleContext context)
  9.     {
  10.     }
  11. }

4.2.3 服務(wù)接口

在4.2.1小節(jié)中,主程序和權(quán)限管理插件在處理服務(wù)時,使用Window這個類作為服務(wù)的契約。這個服務(wù)契約是在.NET Framework中直接定義的,因此主程序和插件都可以訪問到。如果我們新定義的服務(wù)SayHelloService(ISayHelloService接口、SayHelloService服務(wù)實(shí)現(xiàn)類),那么這時候主程序和插件都需要通過接口ISayHelloService來獲取服務(wù),這時候建議將ISayHelloService接口定義到一個外部的程序集,主程序可以引用它,插件也可以依賴它。

4.3 權(quán)限管理的登錄窗體

基于4.2,我們發(fā)現(xiàn)通過服務(wù)可以實(shí)現(xiàn)主程序和插件之間的通訊。當(dāng)主程序獲取到權(quán)限管理注冊的登錄窗體實(shí)例,便獲取該窗體并展現(xiàn)它,此后應(yīng)用系統(tǒng)便交由插件來控制了。

在權(quán)限管理插件的登錄窗體,它由LoginUserControl.xaml來實(shí)現(xiàn),在該頁面的后臺代碼的登錄處理函數(shù)中,一旦登錄成功,它將創(chuàng)建一個主窗體MainWindow,并且顯示該窗體,如下圖所示。

在這里,權(quán)限管理插件創(chuàng)建了主窗體MainWindow類,這個類實(shí)際上是由界面框架插件定義的主窗體。因此,該插件依賴了界面框架插件,并添加了對UIShell.WpfShellPlugin程序集的引用。如下所示。

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

4.4 界面框架插件

應(yīng)用系統(tǒng)由界面框架插件、服務(wù)插件和功能插件構(gòu)成,它們的組合關(guān)系如下所示。

從界面功能上來講,系統(tǒng)由主界面框架、插件中心插件、權(quán)限管理插件、演示插件組成,在其背后還有一些非界面功能插件,比如數(shù)據(jù)庫訪問等。

界面框架插件提供了一個可擴(kuò)展、可組合的界面功能展示。界面框架插件暴露了一個名為UIShell.NavigationService的擴(kuò)展點(diǎn),權(quán)限管理插件、插件中心插件、其它插件則定義了針對該擴(kuò)展點(diǎn)的擴(kuò)展。

界面框架對應(yīng)的擴(kuò)展格式如下所示。該格式由名為Node的XML節(jié)點(diǎn)組成,Node節(jié)點(diǎn)可以嵌套包含子節(jié)點(diǎn)。

 
 
  1.   
  2.         Icon="/UIShell.RbacManagementPlugin;component/Assets/Permission.png"
  3.         Order="490">
  4.     
  5.           Value="UIShell.RbacManagementPlugin.RolePermissionUserControl"
  6.           Icon="/UIShell.RbacManagementPlugin;component/Assets/Role.png" Order="1" />
  7.     
  8.           Value="UIShell.RbacManagementPlugin.UserPermissionUserControl"
  9.           Icon="/UIShell.RbacManagementPlugin;component/Assets/User2.png" Order="2" />
  10.   

當(dāng)界面框架插件沒有加載任何擴(kuò)展時,界面是空白的。左邊導(dǎo)航欄用于加載插件定義的導(dǎo)航菜單,右邊用于加載插件的顯示內(nèi)容。

那么插件中心插件就是由對界面框架插件的擴(kuò)展及如下功能構(gòu)成,如下所示。

插件中心插件對界面框架插件的界面擴(kuò)展是通過如下的Manifest.xml來定義的。

同理,權(quán)限管理插件也是對界面框架插件定義了擴(kuò)展并實(shí)現(xiàn)了如下功能。

權(quán)限管理插件對界面框架的擴(kuò)展定義在Manifest.xml中實(shí)現(xiàn),如下所示。

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

4.4.1 導(dǎo)航服務(wù)

插件對界面框架的擴(kuò)展的XML由導(dǎo)航服務(wù)來進(jìn)行解析。通俗的講,該服務(wù)實(shí)現(xiàn)的是將以下XML節(jié)點(diǎn)變更NavigationNode對象。

 
 
  1.   
  2.         Icon="/UIShell.RbacManagementPlugin;component/Assets/Permission.png" 
  3.         Order="490">
  4.     
  5.           Value="UIShell.RbacManagementPlugin.RolePermissionUserControl" 
  6.           Icon="/UIShell.RbacManagementPlugin;component/Assets/Role.png" Order="1" />
  7.     
  8.           Value="UIShell.RbacManagementPlugin.UserPermissionUserControl" 
  9.           Icon="/UIShell.RbacManagementPlugin;component/Assets/User2.png" Order="2" />
  10.   

NavigationNode對象如下圖所示,它包含子對象。該對象對應(yīng)于XML節(jié)點(diǎn)。我們可以通過INavigationService來獲取這些對象集合。INavigationService會默認(rèn)從名字為"UIShell.NavigationService"的擴(kuò)展點(diǎn)來創(chuàng)建對象。如果我們使用了類似的導(dǎo)航擴(kuò)展定義,但使用了不同的擴(kuò)展點(diǎn),可以使用INavigationServiceFactory來創(chuàng)建指定擴(kuò)展點(diǎn)的導(dǎo)航服務(wù)。

導(dǎo)航服務(wù)還隱藏了針對擴(kuò)展變更事件的處理。該服務(wù)暴露了NavigationChanged事件來通知導(dǎo)航節(jié)點(diǎn)變更。

4.4.2 界面框架擴(kuò)展實(shí)現(xiàn)

界面框架首先需要實(shí)現(xiàn)一個空的布局,其內(nèi)容區(qū)域?yàn)闃浜涂瞻罪@示區(qū)域。樹使用TreeView,空白顯示區(qū)域的父控件是DockPanel。那么,該框架實(shí)現(xiàn)的核心就是將NavigationNode的集合轉(zhuǎn)換成TreeViewNode集合,當(dāng)點(diǎn)擊TreeViewNode時,能夠?qū)⑵鋵?yīng)的用戶控件加載。

界面框架的XAML如下所示。

 
 
  1.     
  2.         
  3.             
  4.                 ……//Status Bars
  5.             
  6.             
  7.                 
  8.                     
  9.                     
  10.                 
  11.                 
  12.                 
  13.                     SelectedItemChanged="NavigationTreeView_SelectedItemChanged" />
  14.                 
  15.                 
  16.                     Text="加載中......"  …… Visibility="Hidden">
  17.                 
  18.                 
  19.             
  20.         
  21.         
  22.         
  23. Background="{DynamicResource WindowBackground}" 
  24. Width="300" HorizontalAlignment="Right" Visibility="Hidden">
  25.             
  26.                 
  27.                     
  28.                         
  29.                         
  30.                     
  31.                     
  32.                     
  33. Grid.Row="0" Margin="16, 16, 16, 0" 
  34. Foreground="{DynamicResource Accent}" FontSize="20" />
  35.                     
  36.                     
  37.                         Name="SideBarDockPanelContent">
  38.                     
  39.                 
  40.                 
  41.             
  42.         
  43.     

從這些XAML片段,你可以看到,LayoutDockPanel這個名字的控件時用于放置動態(tài)加載的插件的控件,加載時機(jī)是在NavigationTreeView的SelectedItemChanged事件。另外,該界面框架還實(shí)現(xiàn)了SideBarDockPanel,用于支持從側(cè)面動態(tài)滑出一個側(cè)邊框。

下面我們看看界面框架針對擴(kuò)展的處理。

接著我們看看ResetNavigation函數(shù)的實(shí)現(xiàn)。

其實(shí)現(xiàn)的核心就是InitializeNavigationTreeView。

該函數(shù)就是根據(jù)NavigationNode集合,遞歸創(chuàng)建TreeViewItem。下面我們來看看點(diǎn)擊樹形導(dǎo)航節(jié)點(diǎn)時,如何動態(tài)加載顯示插件的控件,其核心代碼如下。

從插件動態(tài)加載類型時,我們使用的是node.Bundle.LoadClass,即獲取擴(kuò)展注冊的插件對象,調(diào)用該對象的LoadClass方法來加載用戶控件,然后將用戶控件顯示在LayoutDockPanel控件。

不過,當(dāng)前界面框架還處理一些其它的功能:

(1)當(dāng)前導(dǎo)航節(jié)點(diǎn)的側(cè)邊欄,即當(dāng)切換菜單時,會自動打開/關(guān)閉與其關(guān)聯(lián)的側(cè)邊欄;

(2)緩存與關(guān)閉,即加載用戶控件后,會直接緩存,在切換時,會將前一個控件隱藏,接著顯示當(dāng)前控件;只有關(guān)閉后,用戶控件才從父控件移除掉;

(3)關(guān)閉內(nèi)容區(qū)域與導(dǎo)航節(jié)點(diǎn)選擇的同步,也就是說,關(guān)閉當(dāng)前內(nèi)容后,會默認(rèn)顯示前一個頁面,此時,導(dǎo)航節(jié)點(diǎn)的選擇也必須同步切換;

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

4.5 插件

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

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

在主界面框架中,我們依靠第三方控件庫"ModernUI"來實(shí)現(xiàn)界面,并對"ModernUI"做深入的定制。在界面框架插件引用該控件時,首先,我們需要將該插件添加到Manifest.xml作為本地程序集,即界面框架插件在運(yùn)行時需要與該程序集一起才能夠正常運(yùn)行。

接著,可以直接從bin目錄來引用該程序集或者添加ModernUI源碼項(xiàng)目的程序集引用。

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

 
 
  1. ModernDialog.ShowMessage("Hello, world!", "Hello", MessageButtongs.Ok);

或者

 
 
  1. var dialog = new ModernDialog(){……};
  2. dialog.ShowDialog();

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

在這個WPF應(yīng)用程序,每一個插件在開發(fā)界面時大部分使用了MVVM架構(gòu),它依賴于MVVMLite這個庫。為了能夠讓插件直接使用,并且不需要將其添加到本地程序集的情況下來使用。我們可以在主程序里面直接添加對MVVMLite程序集的依賴,編譯后,每一個插件可以直接來引用主程序輸出目錄下的MVVMLite程序集。

你可以發(fā)現(xiàn),MVVMLite程序集所在的位置。如果是Web應(yīng)用的話,這些程序集所在目錄是bin目錄。這樣的程序集在OSGi.NET框架中成為全局程序集,默認(rèn)開啟支持該功能。你可以通過設(shè)置BundleRuntime.EnableGlobalAssemblyFeature屬性開啟或者關(guān)閉該功能。

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

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

在該界面框架中,所有UI插件都是基于ModernUI控件庫來實(shí)現(xiàn)。該控件庫在界面框架中包含。因此,我們的功能插件需要引用界面框架插件的ModernUI控件庫。

首先,在界面框架,需要將該程序集定義成共享。

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

最后,插件就可以直接通過引用,來添加對該程序集的引用,并在代碼中來調(diào)用了。

4.5.4 插件間的通訊實(shí)現(xiàn)

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

比如,在演示插件,我們引用了配置服務(wù)。配置服務(wù)是在配置服務(wù)插件來創(chuàng)建的,該服務(wù)定義如下所示。

該插件通過Activator來注冊服務(wù)實(shí)例,如下所示。

 
 
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using UIShell.OSGi;
  5. namespace UIShell.ConfigurationService
  6. {
  7.     public class Activator : IBundleActivator
  8.     {
  9.         public void Start(IBundleContext context)
  10.         {
  11.             context.AddService(new ConfigurationService());
  12.         }
  13.         public void Stop(IBundleContext context)
  14.         {
  15.             
  16.         }
  17.     }
  18. }

演示插件依賴于IConfigurationService接口所在的程序集,通過該接口來獲取服務(wù),如下所示。

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

 
 
  1. BundleActivator.ConfigrationService.Set(BundleActivator.Bundle, "TreeViewColumnWidth", 80);

或者

 
 
  1. var width = BundleActivator.ConfigurationService.Get(BundleActivator.Bundle, "TreeViewColumnWidth", 80);

4.5.5 如何從插件動態(tài)的加載類型

從插件加載類型的方式通過插件對象來實(shí)現(xiàn)。插件對象由OSGi.NET框架創(chuàng)建,可以通過插件激活器的IBundleContext.Bundle屬性獲取。

 
 
  1. var bundle = Context.Bundle; // 或者var bundle = Context.GetBundleBySymbolicName("DemoPlugin");
  2. var class = bundle.LoadClass("DemoPlugin.CourseManagementUserControl");

5 關(guān)于框架的藝術(shù)

框架的藝術(shù)并不在于技術(shù)本身,而是在于能夠幫助團(tuán)隊(duì)更有效率的進(jìn)行產(chǎn)品開發(fā)。為了提高產(chǎn)品開發(fā)效率,框架必須能夠提供:

(1)統(tǒng)一的開發(fā)模板:通過模板來規(guī)范團(tuán)隊(duì)成員的編碼規(guī)則與規(guī)范功能模塊的架構(gòu),減少軟件開發(fā)的學(xué)習(xí)成本。比如,我們制作的演示插件模板,在這個模板基礎(chǔ)上做功能開發(fā),是不需要你掌握多少關(guān)于框架本身的技術(shù),而是專注于業(yè)務(wù)實(shí)現(xiàn)及通用功能的調(diào)用;此外,該模板規(guī)范了MVVM架構(gòu)分層,統(tǒng)一了架構(gòu)思想。

(2)一致的用戶體驗(yàn):通過框架為客戶定義了一致的界面風(fēng)格,這使我們的軟件看上起更加的專業(yè)。

(3)良好的分工協(xié)作:通過框架,團(tuán)隊(duì)成員可以專注于不同的功能模塊,進(jìn)行有效率的并行協(xié)作。

6 總結(jié)

這個教程介紹了漂亮界面框架的架構(gòu)、實(shí)現(xiàn)細(xì)節(jié),通過這個教程,你已經(jīng)能夠掌握使用OSGi.NET框架來開發(fā)一個漂亮界面框架了。


分享標(biāo)題:WPF漂亮界面框架實(shí)現(xiàn)原理分析及源碼分享
分享鏈接:http://www.dlmjj.cn/article/dhpipee.html