AvalonDock 2.0+Caliburn.Micro+MahApps.Metro實現Metro風格插件式系統(一)


   隨着IOS7由之前UI的擬物化設計變為如今的扁平化設計,也許扁平化的時代要來了,當然我們是不是該吐槽一下,蘋果什么時候也開始跟風了,自GOOGLE和微軟界面扁平化過后,蘋果也加入了這一隊伍。

 AvalonDock

   AvalonDock 是一個.NET庫,用於在停靠模式布局(docking)中排列一系列WPF/WinForm控件。最新發布的版本原生支持MVVM框架、Aero Snap特效並具有更好的性能。

AvalonDock 2.0版本已經發布了,新版本是用MVVM框架重新編寫,似乎也用了Command(命令)模式。2.0版的文檔尚未發布,但你可以參考Avalon.TestApp 或者2.0版源碼中的Avalon.MVVMTestApp文件夾來查看新的API。

這個庫使用很簡單——只需要用AvalonDock提供的控件包含你自己的控件,頁面布局就立即變成可停靠的(dockable)。可以參考 入門 頁面獲取樣例代碼,了解不同控件的特性。當然你也可以在自己的C#代碼中實例化或操作這些控件。2.0版本中,控件功能與以前一致,但控件名稱已經改變,因此建議參考前述樣例代碼直至參考文檔更新為止。

大名鼎鼎SharpEevelop也應用了AvalonDock,由於SharpEevelop的框架過於龐大,並且SharpEevelop里的AvalonDock 1.3的版本,並不支持MVVM的模式,所以就興起了自己做一個插件式系統,當然也跟一下扁平化的風,目前框架已經做好並應用到個人項目中,本着開源的思想我會把框架搭建的過程,以及遇到的種種問題分享出來。

Caliburn.Micro    

Caliburn是Rob Eisenberg在2009年提出的一個開源框架,可以應用於WPF,Silverlight,WP7等,框架基於MVVM模式,像它的名字一樣,是企業級應用的一把利器。

基於WPF的框架有很多,Prism,WAF等,每個框架都有自己側重點,像Prism側重於模塊間的組合,WAF側重於分層設計。通觀CM的設計,它的一些想法如下: 1.ActionMessage,結合了Blend中的TriggerAction,可以把UI控件中的事件綁定到后台方法,類似於CallMethodAction。CM對ActionMessage進行了很多擴展,包括可以傳入多個參數,參數支持綁定,可以通過CanExecute作執行前判斷並設置控件的Enable等。

2.Conventions,協定,這個詞聽上去有點虛,其實就是智能匹配的意思。CM制定了一系列匹配的規則,比如說View和ViewModel之間的匹配,綁定時傳入控件名可以找到控件,傳入方法名可以綁定到方法等等。

3.Screen和Conductor,作為一個Presentation的框架,各個UI部件(Widget或者叫Pad)的管理是必不可少的。Screen就是用來表示UI部件的,它定義了一些列UI部件的生命期事件,比如Activated,DeActivated等。Conductor是用來管理Screen的,類似於傳統的Controller,不同的Screen可以用一個Conductor來管理,Conductor也使用了策略模式允許更改對Screen的處理。

4.Coroutines,協同程序,定義了一組程序的執行,簡化了異步編程。比如說在網絡中下載圖片並顯示,通常來說需要顯示BusyIndicator,后台線程去網絡讀取圖片,讀取成功后Invoke到UI線程,取消BusyIndicator,顯示圖片。CM提供了一個IResult接口,大大的簡化了異步編程,結合ActionMessage,為AOP的擴展提供了可能。

5.配置性和擴展性,CM移除掉了原Caliburn的一些IOC實現,作為一個通用框架,最常用辦法就是使用工廠模式結合配置文件提供可配置性,使用IOC來解耦組件間的依賴。CM默認是使用MEF來做IOC擴展的,你可以自定義Bootstrapper來使用你喜歡的IOC容器,如Unity等。

6.設計時支持(Design-time support),CM中的ActionMessage是繼承自Blend中的TriggerAction的,也就是說可以在Blend編輯ActionMessage,大大方便了使用。 (這段摘抄了周永恆大大的部分對CM框架的解析,大家想詳細了解的話可以去他的博客去學習,我就不仔細說明了,以后我會用到的地方做說明)

MahApps.Metro  

  這是一個Metro樣式的開源項目,應用該項目可以使你的軟件具有metro的風格,具體就不多說了。  

 

這是測試項目第一階段的運行結果

 

 

言歸正傳,我們從零開始創建項目,下面是整個測試項目的結構:

 

 MefBootstrapper

這是啟動加載類,一般我們WPF程序是從APP.XAML里StartupUri=“****WINDOWS.XAML”來啟動主窗體,但現在由MefBootstrapper擔當了啟動窗體的職責:

 public class MefBootstrapper : Bootstrapper<IShell>
    {
        private CompositionContainer container;

        protected override void Configure()
        {
            /*CompositionContainer 對象在應用程序中有兩種的主要用途。首先,它跟蹤哪些部分可用於組合、它們的依賴項,並且充當任何指定組合的上下文。其次,它提供了應用程序可以啟動組合的方法、獲取組合部件的實例,或填充可組合部件的依存關系。
            部件可直接用於容器,或通過 Catalog 屬性來用於容器。在此 ComposablePartCatalog 中可發現的所有部件都可以供容器來滿足導入,還包括直接添加的任何部件。*/ 
            container = new CompositionContainer(
                new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)))
                );

            var batch = new CompositionBatch();
            var dockScreenManager = new DockScreenManager();
            batch.AddExportedValue<IWindowManager>(new WindowManager());//將指定的導出加入至 CompositionBatch 物件
            batch.AddExportedValue<IDockScreenManager>(dockScreenManager);
            batch.AddExportedValue<IEventAggregator>(new EventAggregator());
            batch.AddExportedValue(container);

            container.Compose(batch);//在容器上執行組合,包括指定的 CompositionBatch 中的更改
        }

        protected override object GetInstance(Type serviceType, string key)
        {
            var contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;//獲取指定類型的規范協定名稱
            var exports = container.GetExportedValues<object>(contract);//返回具有從指定的類型參數派生的協定名稱的已導出對象。如果不是正好有一個匹配的已導出對象,則將引發異常。

            if (exports.Any())
                return exports.First();

            throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
        }

        protected override IEnumerable<object> GetAllInstances(Type serviceType)
        {
            return container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
        }

        protected override void BuildUp(object instance)
        {
            container.SatisfyImportsOnce(instance);//滿足指定的 ComposablePart 對象的導入,而無需注冊該對象以進行重新組合。
        }
    }

由上可知, MefBootstrapper繼承與CM框架提供Bootstrapper<TRootModel>,當Bootstrapper加載時,CM框架便會從MEF容器里尋找出TRootModel類型的實例,並且show出來,也就是我們的主窗體,之后我會把項目源碼放出來,大家可以自己跟蹤OnStartup事件。

我們來看看APP.XMAL

<Application x:Class="DemoApplication.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:DemoApplication"
             >
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary>
                    <local:MefBootstrapper x:Key="bootstrapper" />
                </ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

 

 IShell

  一個簡單的接口,為了方便MEF導出部件

  public interface IShell
    {
    
    }

ShellView.xaml

<MetrolControls:MetroWindow x:Class="DemoApplication.ShellView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:MetrolControls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
        Title="ShellView" Height="500" Width="800">
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Colours.xaml"/>
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml"/>
                <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    <Grid>
        <ContentControl  x:Name="DockContent" Margin="0,2,0,0" Grid.Row="0"/>
    </Grid>
</MetrolControls:MetroWindow>

  可以看到,這時我們引用了MahApps.Metro,MahApps.Metro自定義了一個WINDOWS,在我看來比傳統的和諧那么一點,MahApps.Metro里還有10多種自定義控件,有興趣的可以自己去研究

ShellViewModel

 [Export(typeof(IShell))]
    class ShellViewModel:IShell
    {
        [Import]
        public IScreen DockContent { get; set; }
    }

   一個ShellView.xaml對應一個ShellViewModel,當ShellViewModel標記為Export時,Bootstrapper會把當前程序集所有標記為Export的類導入CM框架的IOC容器里,ShellViewModel相當於ShellView的Datacontext,一個View的加載過程為,由Model找到(CM框架定義了各種查找規則)View,並把Model綁定到View的Datacontext,以后我們UI的邏輯代碼就可以寫在Model里面,並與UI完全分開,這就是我們所說的MVVM模式。上面也有一個典型的View綁定Model里的屬性,細心的可以看到:

 [Import]
  public IScreen DockContent { get; set; }

  該屬性的名稱和ShellView.xaml里的<ContentControl  x:Name="DockContent" Margin="0,2,0,0" Grid.Row="0"/> 的命名完全一樣,奇怪的是我們並沒有寫任何綁定,但DockContent是怎么綁定到View里面的呢,其實綁定的過程已經由CM框架幫我們做了,CM框架會幫助我們把Model里和控件名稱一樣的屬性綁定在一起,這就然我們省了一些事,這只是CM框架的一些小特性。 

  好了,主窗體的說完了,下面我們來看看怎么把AvalonDock融合進去,上面我們說過了,一個Model對應一個View,所以我們要顯示一個UserControl時得生成一對Model-View,

DockView.xaml

<UserControl x:Class="DemoApplication.Views.DockView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             xmlns:avalonDock="http://avalondock.codeplex.com"
             d:DesignHeight="300" d:DesignWidth="800">
    <UserControl.Resources>
        <avalonDock:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
    </UserControl.Resources>
    <Grid>
        <Grid x:Name="layoutRoot">
            <avalonDock:DockingManager  Grid.Row="1" x:Name="dockManager"  AllowMixedOrientation="True"  >
                <avalonDock:DockingManager.Theme>
                    <avalonDock:MetroTheme/>
                </avalonDock:DockingManager.Theme>
                <avalonDock:DockingManager.LayoutItemTemplate>
                    <DataTemplate>
                        <ContentControl IsTabStop="False" />
                    </DataTemplate>
                </avalonDock:DockingManager.LayoutItemTemplate>
                <avalonDock:LayoutRoot>
                    <avalonDock:LayoutPanel  Orientation="Horizontal"  >
                        <avalonDock:LayoutAnchorablePaneGroup DockWidth="200"    Orientation="Vertical"  >
                            <avalonDock:LayoutAnchorablePane  >
                                <avalonDock:LayoutAnchorable Title="left" ></avalonDock:LayoutAnchorable>
                            </avalonDock:LayoutAnchorablePane>
                        </avalonDock:LayoutAnchorablePaneGroup>
                        <avalonDock:LayoutPanel  Orientation="Vertical"  >
                            <avalonDock:LayoutDocumentPaneGroup Orientation="Horizontal">
                                <avalonDock:LayoutDocumentPane   >
                                    <avalonDock:LayoutDocument Title="main"></avalonDock:LayoutDocument>
                                </avalonDock:LayoutDocumentPane>

                            </avalonDock:LayoutDocumentPaneGroup>
                            <avalonDock:LayoutAnchorablePaneGroup DockHeight="100"   Orientation="Horizontal"  >
                                <avalonDock:LayoutAnchorablePane  >
                                    <avalonDock:LayoutAnchorable Title="bottom" ></avalonDock:LayoutAnchorable>
                                </avalonDock:LayoutAnchorablePane>
                            </avalonDock:LayoutAnchorablePaneGroup>
                        </avalonDock:LayoutPanel>
                        <avalonDock:LayoutAnchorablePaneGroup DockWidth="200"    Orientation="Horizontal"  >
                            <avalonDock:LayoutAnchorablePane >
                                <avalonDock:LayoutAnchorable Title="Right" ></avalonDock:LayoutAnchorable>
                            </avalonDock:LayoutAnchorablePane>
                        </avalonDock:LayoutAnchorablePaneGroup>
                    </avalonDock:LayoutPanel>

                </avalonDock:LayoutRoot>
            </avalonDock:DockingManager>
        </Grid>
    </Grid>
</UserControl>

 

這是VS2012設計器的顯示


這些東西的學習周期還是有的,我就不一一去說。有些東西只可意會不可言傳。

DockViewModel

    [Export(typeof(IScreen))]
    public class DockViewModel : Screen
    {

    }

 

  我們可以看到ShellViewModel里的DockContent就是IScreen類型的,由於標記為Import,所以程序會自動幫我們把MEF容器里IScreen類型注入,所以其實DockContent就是DockView,我這里為了方便直接用了CM框架的IScreen,如果有兩個類標記為[Export(typeof(IScreen))],就會導致程序異常,因為有兩個實例。程序不知道該導出哪個,所以我們之后會定義另一個接口,該接口只有唯一一個類即唯一的DockViewModel標記為導出,因為我們DockView就是唯一的,導入和導出部件這是MEF的知識,MEF是什么大家可以百度學習,CM框架默認是MEF作為容器。

MEF

  Managed Extensibility Framework(MEF)是.NET平台下的一個擴展性管理框架,它是一系列特性的集合,包括依賴注入(DI)以及Duck Typing等。MEF為開發人員提供了一個工具,讓我們可以輕松的對應用程序進行擴展並且對已有的代碼產生最小的影響,開發人員在開發過程中根據功能要求定義一些擴展點,之后擴展人員就可以使用這些擴展點與應用程序交互;同時MEF讓應用程序與擴展程序之間不產生直接的依賴,這樣也允許在多個具有同樣的擴展需求之間共享擴展程序

第一階段就先這樣,以后我會慢慢更新,直道整個插件系統的完成

 

 

如果您看了本篇博客,覺得對您有所收獲,請點擊右下角的 [推薦]

如果您想轉載本博客,請注明出處

如果您對本文有意見或者建議,歡迎留言

感謝您的閱讀,請關注我的后續博客

作者:Zengg 出處:http://www.cnblogs.com/01codeworld/

本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM