一、從GitHub瀏覽Prism示例代碼的方式入門WPF下的Prism


最近這段時間一直在看一個開源軟件PowerToys的源碼,里面使用Modules的開發風格讓我特別着迷,感覺比我現在寫代碼的風格好了太多太多。我嘗試把PowerToys的架構分離了出來,但是發現代碼維護量比較大,我自己很難維護這一套東西,就想到了同類型的Prism。
之前一直使用MVVMLight進行開發。因為最近要寫一個開源的財務軟件,想在項目中使用Prism,所以這個系列是財務軟件的前置系列Prism從0到入門。主要是對照Prism官方例子去學一遍他的代碼並整理成自己的代碼。然后嘗試理解Prism中體現的編程思想。以及如何在我們自己的項目中更好的使用Prism。

PrismLibrary/Prism-Samples-Wpf: Samples that demonstrate how to use various Prism features with WPF (github.com)

打開連接后有一個README.md。里面是Prism Samples WPF。1到29個示例包含了Prism的主要內容。這個系列我們就學習這個,並在學習完成后結合實踐到自己的項目中。

一、瀏覽PrismSamples中的目錄結構了解怎么學Prism;

簡單瀏覽了一下,發現第07的Modules 分為了5種不同的方式,需要梳理的時間比較多,而01-06內容比較少,第一篇就先寫到07之前,我們看到示例代碼中BootstrapperShell(引導程序外殼)、Regions(區域)、CustomRegions(自定義區域)、ViewDiscovery (我理解為Register View WithRegion區域選項卡查看,把某個視圖通過RegionManager放在某個位置)、ViewInjection(視圖注入)、ViewActivationDeactivation(視圖的激活和取消激活)。

現在開始學習示例代碼。打開01BootstrapperShell的代碼,我們進行觀察;

1、App.xaml中主要修改

刪掉了StartupUri屬性,沒有額外的代碼。

2、App.cs中主要修改

App依然繼承自Application,在重寫OnStartup中實例化了Bootstrapper類,然后調用Bootstrapper實例后的對象的.Run()方法進行啟動。

3、查看BootStrapper.cs

雙擊BootStrapper.cs打開源碼看到了。包含了2個重寫的方法CreateShell()方法返回一個DependencyObject。和一個沒有返回值的RegisterTypes()方法。在學習WPF教程四的時候,我們講到了DependencyObject對象,我們了解到DependencyObject所有的子對象都支持依賴項屬性。包括UI控件。而CreateShell()方法返回的是一個DependencyObject類型,傳入了Views文件夾下的MainWindow.xaml。

4、在MainWindow.xaml中主要變化

只有一個ContentControl,顯示一串字符串“Hello from Prism”。

5、運行代碼

發現打開了一個標題為Shell的窗體顯示了Hello from Prism。就是我們的MainWindow。這里CreateShell()函數就是修改啟動起始頁面的地方。先不分析,繼續看下一個Samples。

protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }

總結例子01:在CreateShell()中傳入的MainWindow是設置默認啟動頁面,使用容器解析了MainWindw 並返回。(我們只學習代碼,不考慮設計啟動邏輯,單例等等。不在這里學習,只學Samples代碼。)

觀察02Regions示例;

1、App .xaml中主要修改

App.xaml中 添加命名空間 xmlns:prism="http://prismlibrary.com/";

更換Application為prism:下的PrismApplication。

移除了StartupUri屬性。

2、App.cs中主要修改

修改App繼承自PrismApplication;

重寫CreateShell()方法;

返回結果是使用Container.Resolve ()容器解析出來的MainWindow窗體。

 protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }

Views下的MainWindow.xaml

創建了一個ContentControl控件,用於顯示內容,並添加了附加依賴項屬性prism:RegionManager.RegionName="ContentRegion" 。cs代碼中無更新內容。

啟動程序,界面無任何內容。

總結:在ContentControl控件中添加了附加依賴項屬性,從命名看,是區域管理類下的區域名稱設置為ContentRegion。

觀察03CustomRegions示例;

1、App.xaml中主要修改

添加了 xmlns:prism="http://prismlibrary.com/"命名空間;修改了Application類為PrismApplication。

2、App.cs中主要修改

修改了App繼承自PrismApplication。重寫了CreateShell()方法,並且返回值修改為Window,同樣使用了Container.Resolve ()的方式設置默認啟動窗體。重寫了ConfigureRegionAdapterMappings()方法,我們在PrismApplication使用F12跳轉過去,看到在PrismApplication下沒有ConfigureRegionAdapterMappings()方法,繼續PrismAoolicationBase上F12,我們看到了ConfigureRegionAdapterMappings()方法點開注釋,看到提示(翻譯后):配置要在應用程序中使用的默認區域適配器映射,以便使XAML中定義的UI控件適配為使用區域並自動注冊。可以在派生類中重寫,以添加應用程序所需的特定映射。 返回結果(翻譯后)為包含所有映射的Prism.Regions.RegionAdapterMappings實例。在搭配這個方法名和參數,我們大概了解到這個方法應該是配置區域適配器映射。我們搭配代碼詳細看一下。調用了父類的ConfigureRegionAdapterMappings()方法后,用使用傳入參數的regionAdapterMappings.RegisterMapping()方法,傳入了stackPanel,和Container.resolve (),使用regionAdapterMappings注冊一個容器解析的StackPanelRegionAdapter的類。因為regionAdapterMappings是傳入的對象,StackPanel又是布局的面便,所以我們去分析StackPanelRegionAdapter。

 protected override void ConfigureRegionAdapterMappings(RegionAdapterMappings regionAdapterMappings)
        {
            base.ConfigureRegionAdapterMappings(regionAdapterMappings);
            regionAdapterMappings.RegisterMapping(typeof(StackPanel), Container.Resolve<StackPanelRegionAdapter>());
        }

3、分析StackPanelRegionAdapter

我們看到StackPanelRegionAdapter是是在Prism文件夾。繼承自RegionAdapterBase

一個公共的空的構造函數;

重寫Adapt方法,並且傳入了IRegion和StackPanel類型的參數,我們使用F12看到region.Views主要是獲取區域中視圖集合,所有添加視圖的集合對象。這方法中使用傳入的region.Views注冊了CollectionChanged事件,這個事件主要是處理集合變更的消息;從這段代碼看主要邏輯是傳入的region如果集合發生了變化,如果是Add操作則把時間傳入的控件添加到regionTarget中。

  protected override void Adapt(IRegion region, StackPanel regionTarget)
        {
            region.Views.CollectionChanged += (s, e) =>
            {
                if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
                {
                    foreach (FrameworkElement element in e.NewItems)
                    {
                        regionTarget.Children.Add(element);
                    }
                }

                //handle remove
            };
        }

一個CreateRegion()方法,F12看方法說明,是創建一個新的IRegion實例,用於調整對象。

  protected override IRegion CreateRegion()
        {
            return new AllActiveRegion();
        }

3、Views 下的MainWindow.xaml

我們看到在MainWindow下有一個StackPanel。設置了一個附加依賴項屬性,並命名為ContentRegion。cs文件中什么也沒有新增。

 <Grid>
        <StackPanel prism:RegionManager.RegionName="ContentRegion" />
    </Grid>

4、我們運行代碼。界面什么也沒有。

總結:這個例子在講如何注冊StakPanel,並通過在App下重寫ConfigureRegionAdapterMappings()方法的方式,使用 regionAdapterMappings.RegisterMapping(typeof(StackPanel), Container.Resolve ());的方式設置自定義面板區域適配器,然后再UI的XAML中通過附加依賴項屬性的方式regionManager.regionName設置Name。先總結到這里,我們去看一下篇。

觀察04ViewDiscovery示例

1、App.xaml

添加命名空間xmlns:prism="http://prismlibrary.com/"

修改Application為prism:PrismApplication

去掉StartupUri屬性

2、App.cs

重寫CreateShell()方法。返回MainWindow。

 protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }

3、Views下的MainWindow.xaml

添加了ContentControl控件,設置了附加依賴項屬性 prism:RegionManager.RegionName="ContentRegion"

4、Views下的MainWindow.cs

再構造函數中,使用傳入的regionManager使用RegisterViewWithRegion方法,傳入了一個ContentRegion字符串,和ViewA。

 public MainWindow(IRegionManager regionManager)
        {
            InitializeComponent();
            //view discovery
            regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA));
        }

我們在RegisterViewWithRegion上F12看注釋,通過注冊類型將視圖與區域相關聯。當顯示區域get時,將使用ServiceLocator將此類型解析為一個具體實例。實例將被添加到區域的視圖集合中,兩個參數分別是與視圖關聯的區域的名稱,要注冊到的視圖的類型;

我就知道了,這里原來時使用regionMananger去注冊關聯一個區域名稱,和一個視圖對象。ContentRegion字符串是我們的ContentControl控件的附加依賴項屬性,ViewA是我們的一個UserControl。

4、View下的ViewA.xaml

包含了一個38號大的字體顯示內容為View A的TextBlock控件。CS文件中沒有更改。

5、運行程序

界面上顯示了View A。

總結:App啟動時通過重寫CreateShell()方法使用Container.Resolve方法返回一個MainWindow;MainWindow界面有一個ContentControl顯示控件,有一個附加依賴項屬性,設置了區域名稱為ContentRegion。在MainWindow的有參構造函數中使用區域管理器,注冊了視圖和顯示區域的關聯,傳入了區域名稱和視圖類。視圖類是自己的顯示內容。通過這樣的流程在MainWindow下通過RegisterViewWithRegion方法,關聯了具有ContenRegion名稱的附加依賴項屬性和視圖類,界面上就顯示了關聯的視圖。

觀察05ViewInjection示例

1、App.xaml

添加了xmlns:prism="http://prismlibrary.com/" 命名空間

修改Application為prism:PrismApplication。

去掉StartupUri屬性

2、App.cs

App繼承自PrismApplication

重寫了CreateShell()方法,返回對象為Container.Resolve (); 解析后的MainWindow窗體,

3、Views下的MainWindow.xaml

創建了DockPanel ,設置最后一個元素填充剩余空間。創建Button按鈕,填充在DockPanel面板的頂部,顯示為Add View,創建Click事件,創建了ContentControl顯示控件,設置附加依賴項屬性RegionName,名字叫做ContentRegion。

   <DockPanel LastChildFill="True">
        <Button DockPanel.Dock="Top" Click="Button_Click">Add View</Button>
        <ContentControl prism:RegionManager.RegionName="ContentRegion" />
    </DockPanel>

4、Views下的MainWindow.cs

使用F12跳轉到對應的接口說明

IContainerExtension _container 對象是對容器的抽象。

IRegionManager _regionManager; 是定義一個接口來管理一組區域並將區域附着到對象;

構造函數中初始化了這2個對象,使用是在這個Button的點擊事件中,我們看到了使用容器對象Resolve解析了ViewA界面,然后使用了IRegionManager對象找到了在XAML中定義的附加依賴項屬性名為ContentRegion的控件,然后使用Add方法添加了容器中的ViewA

 public partial class MainWindow : Window
    {
        IContainerExtension _container;
        IRegionManager _regionManager;

        public MainWindow(IContainerExtension container, IRegionManager regionManager)
        {
            InitializeComponent();
            _container = container;
            _regionManager = regionManager;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var view = _container.Resolve<ViewA>();
            IRegion region = _regionManager.Regions["ContentRegion"];
            region.Add(view);
        }
    }

5、Views下的ViewA.xaml

是一個UserControl,里面只有一個TextBlock 顯示內容為View A 字號為38。cs文件中沒有額外的東西。

6、運行代碼,界面顯示View A

總結:在程序啟動時更改Application類為PrismApplication,在重寫CreateShell()方法時使用Container解析MainWindow窗體,並返回,作為啟動窗體,在MainWindow被啟動時,在構造函數中初始化了容器接口和區域管理對象。MainWindow界面中設置了顯示控件和點擊控件、顯示控件設置了附加依賴項屬性,用於做區域名稱ContentRegion。在點擊事件中,使用容器接口解析ViewA用戶控件,並使用區域管理器查找名字為ContentRegion的區域(我們界面上的ContentControl),然后再調用這個區域返回對象的Add方法再找到的這個區域中顯示我們剛才解析的ViewA自定義控件。

觀察06ViewActivationDeactivation示例

1、App.xaml

替換Application為PrismApplication,使用Prism框架的時候,我看到現在每個都替換了。所以這一部分后面再寫到的時候我都...處理。

(和前面一樣的部分我就省略了)

2、App.cs

重寫CreateShell(),解析MainWindow並返回,作為啟動頁面。

(....)

3、MainWindow.xaml

創建DockPanel,用StackPanel嵌套4個Button,並 設置了顯示控件ContenControl,添加附加依賴屬性RegionName為ContentRegion。Button 從名字理解,主要是激活ViewA (Button_Click)取消激活ViewA(Button_Click_1);激活ViewB(Button_Click_2) 取消激活ViewB(Button_Click_3)。

  <DockPanel LastChildFill="True">
        <StackPanel>
            <Button Content="Activate ViewA" Click="Button_Click"/>
            <Button Content="Deactivate ViewA" Click="Button_Click_1"/>
            <Button Content="Activate ViewB" Click="Button_Click_2"/>
            <Button Content="Deactivate ViewB" Click="Button_Click_3"/>
        </StackPanel>
        <ContentControl prism:RegionManager.RegionName="ContentRegion" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </DockPanel>

4、Views下的MainWindow.cs

定義了變量容器接口對象,區域管理對象,區域對象和兩個ViewA、ViewB頁面對象;

構造函數中初始化了容器接口和區域管理對象、注冊了loaded事件;

再Loaded事件中,使用容器解析ViewA和ViewB並放入類的ViewA和ViewB對象;

使用區域管理器獲取界面上的ContentRegion區域對象,復制給類的_region對象,並再區域中添加ViewA和ViewB視圖;

Button下4個Click使用_region的Activate和Deactivate方法顯示對應的ViewA和ViewB;

 private void Button_Click(object sender, RoutedEventArgs e)
        {
            //activate view a
            _region.Activate(_viewA);
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            //deactivate view a
            _region.Deactivate(_viewA);
        }

        private void Button_Click_2(object sender, RoutedEventArgs e)
        {
            //activate view b
            _region.Activate(_viewB);
        }

        private void Button_Click_3(object sender, RoutedEventArgs e)
        {
            //deactivate view b
            _region.Deactivate(_viewB);
        }

5、Views下的ViewA.xaml和ViewB.xaml無特殊代碼,就是顯示對應的ViewA和ViewB;

6、運行代碼,點擊不同的Button可以切換顯示ViewA和ViewB,

總結:MainWindow在構造函數中初始化了容器接口和區域管理對象,

在loaded事件中初始化了用於顯示的容器解析對象ViewA和ViewB,使用區域管理區查找我們在界面上通過附加依賴項屬性定義的ContentRegion顯示控件,並返回給一個IRegion區域對象,使用IRegion區域對象的Add方法,添加資源到區域中。

在4個Button的Click事件中,主要是通過Activate和Deactivate方法來設置顯示不同的ViewA和ViewB;

因為07示例分為了5個不同加載Modules方式,我們第一篇就先學到這里。下面打開我們最開始創建的WPFPrismDemo,我們寫一下代碼回憶一下前面的內容;

我們通過一個DEMO來掌握01-06的示例內容。

二、創建PrismSamplesDemo

打開VS創建WPF項目,我使用了.NET Framewrok 4.8框架。

打開PrismLibrary/Prism: Prism is a framework for building loosely coupled, maintainable, and testable XAML applications in WPF, Xamarin Forms, and Uno / Win UI Applications.. (github.com) 這個是Prism的地址

我們看到了。在WPF下,有一個Prism.DryIoc和Prism.Unity,我看評論說都說DryIoc的性能比Unity的好,所以我選用了DryIoc(我並沒有驗證這個性能問題,我只是找一個入手開始進入學習。)

在創建的PrismSamplesDemo項目中點擊解決方案右鍵=》管理Nuget包=》瀏覽中輸入Prism.DryIoc=》選擇安裝最新版。就完成了Prism的安裝,接下來我們學習示例。

1、修改App.xaml

添加xmlns:prism="http://prismlibrary.com/"命名空間

修改Application為prism:PrismApplication

移除StartupUri屬性。

2、修改App.cs

修改App繼承自PrismApplication

重寫CreateShell()用於設置初始顯示窗口

  protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }
        //這個方法如果不重寫則會編譯報錯。
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
             
        }

如果不重寫RegisterTypes的話,編譯會報錯。

3、修改MainWindow.xaml,使用ContentControl嘗試設置顯示區域。

<Window x:Class="WPFPrismDemo.MainWindow"
        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://prismlibrary.com/"
        xmlns:local="clr-namespace:WPFPrismDemo"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <DockPanel>
        <StackPanel>
            <Button Content="Activate View A" Click="ActivateViewA_Click"/>
            <Button Content="Deactivate View A" Click="DeactivateViewA_Click"/>
            <Button Content="Activate View B" Click="ActivateViewB_Click"/>
            <Button Content="Deactivate View B" Click="DeactivateViewB_Click"/>
        </StackPanel> 
        <ContentControl  prism:RegionManager.RegionName="ContentRegion" FontSize="30" VerticalAlignment="Center" HorizontalAlignment="Center"/>
    </DockPanel>
</Window>

這里你可以嘗試使用其他的控件替換掉ContentControl,會報錯。這個就是03示例里講到的CustomRegions,Prism中實現了幾個,但是有一些沒有實現,所以需要自己寫了,就參考02的例子,去寫自己的就可以。

4、添加Views文件夾,並添加ViewA和ViewB用戶控件,用於作為顯示對象。

ViewA 添加TextBlock 顯示ViewA

ViewB 添加TextBlock 顯示ViewB

5、修改MainWindow.cs

不要看06示例的代碼,憑理解自己去寫,有報錯,就去對照示例代碼,我在寫的時候忘記了IContainerExtension怎么拼寫、忘記了_container.Resolve解析視圖后需要在__region中add這對象。不然無法Activate和Deactivate。

代碼如下:

using Prism.Ioc;
using Prism.Regions;
using System.Windows;
using WPFPrismDemo.Views;

namespace WPFPrismDemo
{
    /// <summary>
    /// MainWindow.xaml 的交互邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        IContainerExtension _container;
        IRegionManager _regionManager;
        IRegion _region;
        ViewA _viewA;
        ViewB _viewB;
        public MainWindow()
        {
            InitializeComponent();
        }
        public MainWindow(IContainerExtension container, IRegionManager regionManager)
        {
            InitializeComponent();
            _container = container;
            _regionManager = regionManager;
            this.Loaded += MainWindow_Loaded;
        }

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            _region = _regionManager.Regions["ContentRegion"];
            _viewA = _container.Resolve<ViewA>();
            _viewB = _container.Resolve<ViewB>();
            _region.Add(_viewA);
            _region.Add(_viewB);
        }

        private void ActivateViewA_Click(object sender, RoutedEventArgs e)
        {
            _region.Activate(_viewA);
        }

        private void DeactivateViewA_Click(object sender, RoutedEventArgs e)
        {
            _region.Deactivate(_viewA);
        }

        private void ActivateViewB_Click(object sender, RoutedEventArgs e)
        {
            _region.Activate(_viewB);
        }

        private void DeactivateViewB_Click(object sender, RoutedEventArgs e)
        {
            _region.Deactivate(_viewB);
        }
    }
}

03示例的CustomRegions,目前我們還沒有用到,所以這里先不做例子去分析了。

以上是01-06示例的學習,一共有29個示例。01-06我們學習了怎么修改PrismApplication、怎么重寫啟動頁面,怎么設置區域附加依賴項屬性,怎么實例化IRegionManager、IContainerExtension、IRegion,怎么使用Resolev、_region.Add、和Activate、Deactivate。目前就先學習這么多。

我創建了一個C#相關的交流群。用於分享學習資料和討論問題,這個propuev也在群文件里。歡迎有興趣的小伙伴:QQ群:542633085


免責聲明!

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



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