Prism - 體會Shell和Module

19 March 2012 — Written by Sky Chang
#Silveright#WPF

這篇的內容,主要是翻譯於這個網站的內容,是由國外的Nick Polyak高手所寫的,然後再加上小弟雜多的解釋內容XDD,所以內容不是百分百和原網站一樣,也請各位多多包涵 ( 所以這篇可以算是讀後心得了,不算翻譯文了.. )

最簡單!有xaml的Prism程式

上一篇,示範了完全沒有xmal的程式,利用完全沒有xaml的程式來專注於Bootstrapper上;而這篇,會示範一個最簡單,有xaml、Shell、Module的Prism程式,如果沒看過前篇,建議看一下會比較好,因為有很多的細節( 例如:Module專案怎麼建、要參考那些dll,有些不要copy local)的設定,不會再這一篇出現了。

什麼叫做最簡單呢!?運作出來的結果如下,我們可以看到畫面非常的單純( 和上一篇比,至少有字了 ),只有兩串字,最上面是整個Shell,而中間紅色的字,則是利用Prism嵌入進來的Module1。

image

Shell

Shell在Prism裡面,就像一個重劃區土地一樣,負責提供最底層的地;而地裡面會再區分一個又一個的Region,每個Region上面就可以把Module蓋上去,就像是蓋個新光、大遠百之類的建築物。

Shell和Module的XAML

開始前,我們會先把放置Shell的Main Project和放置Module的Module Project準備好;當建立這兩個專案後,預設每個專案一定都會有一個MainPage.xaml,而這邊,我們分別把這兩個專案的MainPage.xaml改名,Main Project的部分,當然是改成Shell.xaml嚕,而Module的部分,就改成Module1View.xaml ( 再次提醒,如果沒看過前篇的人,建議看一下,因為有很多的細節需要處理。 );而下面是先列出Shell和Module的XAML,以下是Shell的部分,我們可以看到,其實沒有多大的變化,只是我們多增加了prism的namespace,並且在ContentControl裡面,定義了MyRegion1的Region Name。

<UserControl x:Class="PrismDemo2HaveVisual.Shell"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:prism="http://www.codeplex.com/prism">

    <Grid x:Name="LayoutRoot" Background="White">
        <TextBlock Text="這裡是Shell,不是Module"
                   FontSize="25"
                   Foreground="Blue"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Top"/>
        <!-- 放置Region,並給個名子-->
        <ContentControl HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        prism:RegionManager.RegionName="MyRegion1"/>
    </Grid>
</UserControl>

接下來是Module的XAML,這部分就很簡單,裡面只放入一個TextBlock,其他沒甚麼改變。

<UserControl x:Class="Module1.Module1View"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">

    <TextBlock Text="我是Module1"
               FontSize="25"
               Foreground="Red" 
               />
</UserControl>

接下來,我們來看看程式碼的部分。

Shell和Module的程式碼

Shell.cs的部分也很簡單,我們只要在Shell Class上面加上Export這個屬性,讓MEF知道要利用這個做輸出。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel.Composition;

namespace PrismDemo2HaveVisual
{
    [Export]
    public partial class Shell : UserControl
    {
        public Shell()
        {
            InitializeComponent();
        }
    }
}

接下來,我們看看Module的部分,這部分也只需要加上Export屬性。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel.Composition;

namespace Module1
{
    [Export(typeof(Module1View))]
    public partial class Module1View : UserControl
    {
        public Module1View()
        {
            InitializeComponent();
        }
    }
}

就這樣,就完成了視覺的部分。

建立Bootstapper Class

這個部份我相信大家應該都很有經驗了,大家可以直接看看下面的程式碼,比較特別的是ConfigureAggregateCatalog的方法裡面加了一行程式碼,我下面有很長的註解,如果看不太懂也沒關係,下一篇會針對原始碼做解說,這邊只要記住,是把Shell註冊進MEF就對了;其他部分則沒甚麼改變。

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Practices.Prism.MefExtensions;
using System.ComponentModel.Composition.Hosting;
using Microsoft.Practices.Prism.Modularity;

namespace PrismDemo2HaveVisual
{
    public class TheBootstrapper : MefBootstrapper
    {
        protected override void InitializeShell()
        {
            base.InitializeShell();
            //將Shell指定給Application的RootVisual,
            //也就是說,此Application的最上層就是Shell了。
            Application.Current.RootVisual = (UIElement) Shell;
        }

        protected override DependencyObject CreateShell()
        {
            //產生Shell。
            return Container.GetExportedValue();
        }

        protected override void ConfigureAggregateCatalog()
        {
            base.ConfigureAggregateCatalog();

            //這裡很有趣,AggregateCatalog這個變數是一個AggregateCatalog型別( 別懷疑,原始碼就是這樣,大小寫都一樣 )
            //而我們這邊要多加一個AssemblyCatalog進去這個Collection裡面,
            //而使用new AssemblyCatalog帶入的參數,會自動去搜尋這個Assembly裡面有符合的AssemblyCatalog,
            //我們的目的就是希望把Shell加入進去。
            //那為何用this呢?,因為這裡面就含有Shell( 也就是Shell.xaml.cs ),
            //而這個Shell的程式碼裡面有Export,而這個Export就是這裡MEF需要的。
            //但是Shell和這個類別好像沒關聯阿!?
            //原因是因為this.GetType().Assembly這個地方,Assembly代表的是組件,
            //換言之,他搜尋的層級並非只是這個Class.而是整個NameSpace的層級,
            //剛好,好死不死,Shell和這個class的NameSpace是一樣的。

            this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(this.GetType().Assembly));
        }

        protected override IModuleCatalog CreateModuleCatalog()
        {
            //最先進來
            ModuleCatalog moduleCatalog = new ModuleCatalog();

            //加入模組,這裡只是單純的加入模組,並不會決定放置的位置。
            moduleCatalog.AddModule
            (
                new ModuleInfo
                {
                    InitializationMode = InitializationMode.WhenAvailable,
                    Ref = "Module1.xap",
                    ModuleName = "Module1Impl",
                    ModuleType = "Module1.Module1Impl, Module1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
                }
            );

            return moduleCatalog;
        }
    }
}

到這邊,Bootstrapper就算完成了。

更改App.xaml.cs

我們改用TheBootstrapper為主要的啟動物件。

private void Application_Startup(object sender, StartupEventArgs e)
    {
        (new TheBootstrapper()).Run();
    }

最後,我們要建立一個實作IModule的物件。

建立實作IModule的物件Module1Impl

同樣的,我們最後必須建立一個實作IModule的物件,當此物件被建構的時候,會去利用MEF來注入一個實作IRegionManager的類別TheRegionManager,而在運作整個Prism的時候,並會利用TheRegionManager來將這個Module注入到Shell裡面的特定位置。

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Prism.MefExtensions.Modularity;
using System.ComponentModel.Composition;
using Microsoft.Practices.Prism.Regions;

namespace Module1
{
    [ModuleExport(typeof(Module1Impl))]
    public class Module1Impl : IModule
    {

        [Import]
        public IRegionManager TheRegionManager { private get; set; }

        #region IModule Members

        public void Initialize()
        {
            //由外面傳進來的RegionManager,RegionManager用來控制此View要與哪個XAML的Region做關聯。
            TheRegionManager.RegisterViewWithRegion("MyRegion1", typeof(Module1View)); 
        }

        #endregion
    }
}

就這樣,常常得撰寫過程後,就完成了,整個專案的文件夾,會如下圖。

image

後記

如前面所說,Prism的入門比較高,但是好處卻是很大的,藉由Module的拆開,我們就可以很輕鬆地拆開來開發,而不會整個架構弄得亂七八糟,測試等等的流程,也會簡單許多,程式碼也會乾淨許多,但學習曲線是比較高的;最後,寫完這篇,或是看完這篇的人,或許還會有許多疑問,這時候,我們就準備要進入Bootstrapper的原始碼來看看了,下一篇,會介紹比較底層的架構,相信看完,會更加的了解,為什麼要這樣做。

參考資料

Sky & Study4.TW