三、從GitHub瀏覽Prism示例程式碼的方式入門WPF下的Prism之Mvvm的08-12示例

這一篇是學習了前2篇RegionManager關聯視圖,和通過不同的方式載入Module示例之後的開始進入MVVM了。

從第08示例開始,進入了MVVM部分。

從08示例開始學習Prism下的MVVM思想

觀察08-ViewModelLocator示例

08示例只有一個工程,添加了Prism.Unity的包

1、分析ViewModelLocator工程

1.1、App.xaml

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

修改了Application為prism:PrismApplication

移出了StartupUri屬性

1.2、App.cs

修改了App繼承自PrismApplication

重寫CreateShell()設置主頁面

重寫RegisterTypes()

1.3、Views下的MainWindow.xaml

添加了顯示控制項ContentControl並設置了附加依賴項屬性,區域名稱:ContentRegion

設置和Title{Binding Title}

設置了prism:ViewModelLocator.AutoWireViewModel=”True” 屬性。

cs文件下無新增程式碼

1.4、ViewModels文件夾下的MainWindowVieModel.cs

MainWindowViewModel繼承自Prism.Mvvm.Bindable類,該類繼承自INotifyPropertyChanged

並創建了屬性Title並在Set的時候調用了SetProperty實現基於Bindable封裝的屬性通知。

2、運行程式碼

介面標題顯示Prism Unity Application;

總結:在工程中引用Prism.Unity後,不需要額外去寫DataContent程式碼,就可以自動關聯ViewModel和View。ViewModel 又有封裝比較好的BindableBase。只需要在VM下繼承自BindableBase就可以了,至於如何關聯上ViewModel和View的,這裡還沒有深入了解,只是分析示例,沒有看到有額外配置的地方,憑經驗MVC的經驗,感覺是同名ViewModels和View。需要寫程式碼驗證;

分析09-ChangeConvention示例

ChangeConvention示例的工程名字為ViewModelLocator,只有一個工程,添加了對Prism.Unity包的引用

1、ViewModelLocator工程

1.1、App.xaml

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

修改了Application繼承自prism:PrismApplication

移除了StartupURI屬性

1.2、App.cs

重寫了CreateShell(),使用Container.Resolve解析MainWindow作為返回主窗體

重寫了ConfigureViewModelLocator()方法,主要的內容都在這裡。把斷點打 var viewName = viewType.FullName;這一行,我們觀察在幹什麼,

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

            ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
            {
                var viewName = viewType.FullName;
                var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
                var viewModelName = $"{viewName}ViewModel, {viewAssemblyName}";
                return Type.GetType(viewModelName);
            });
        }

我們首先觀察方法ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver()

F12跳轉過去之後,從注釋上理解是將默認視圖類型設置為視圖模型類型解析程式。

通過重寫ConfigureViewModelLocator()方法,在這裡重新關聯View和ViewModel的關係。

通過上面的截圖我們看到了ViewName、ViewModelName被重新做了關聯,ViewModel被指定到了ViewModelLocator.Views.MainWindowViewModel。我們在Views目錄下看到了MainWindowViewModel.cs。

1.3、Views下的MainWindow.xaml

設置了顯示控制項的ContentControl設置了RegionName為ContentRegion

設置了窗口的Title綁定{binding Title}

設置了Prism:ViewModelLocator.AutoWireViewModel=”True”

cs文件中無修改

1.4、Views下的MainWindowViewModel.cs

繼承自BindableBase。 創建了屬性Ttile使用SetProperty進行屬性更改通知。

1.5、運行程式碼

標題修改為Prism Unity Application

總結:這個Demo主要是在講解在App.cs下重寫ConfigureViewModelLocator()方法,重新設置了View和對應ViewModel的關聯。一般情形下,這個應該用不到把,畢竟Views和ViewModels是2個獨立的文件夾。

分析10-CustomRegistrations示例

經過了前面這麼多例子的學習,現在開始加快進度,每個工程打開後我們都會梳理一遍工程,主要梳理工程引用關係、引用的包、載入啟動項,後續的過程中如果沒有額外需要注意的,那麼梳理過程教程中就省略了,因為前面這麼多例子下來,應該都知道關注哪些內容了。

1、Views下的MainWindow.xaml

prism:ViewModelLocator.AutoWireViewModel=True;

Title={binding Title}

添加ContentControl並設置了附加依賴項屬性RegionName為ContentRegion,cs文件中無新增程式碼

2、ViewModels 下的CustomViewModel.cs

繼承自Prism.Mvvm.BindableBase

創建屬性Title 並在set中使用SetPriperty實現通知;

3、重寫ConfigureViewModelLocator()

程式碼中提供了4種方法。

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

            // type / type
            //ViewModelLocationProvider.Register(typeof(MainWindow).ToString(), typeof(CustomViewModel));

            // type / factory
            //ViewModelLocationProvider.Register(typeof(MainWindow).ToString(), () => Container.Resolve<CustomViewModel>());

            // generic factory
            //ViewModelLocationProvider.Register<MainWindow>(() => Container.Resolve<CustomViewModel>());

            // generic type
            ViewModelLocationProvider.Register<MainWindow, CustomViewModel>();
        }

從程式碼中直觀來看這四種都是實現一個事情的,第四種看上去最簡潔,所以,我們用第四種,使用ViewModelLocationProvider類的Register關聯2個對象。一個View、一個ViewModel。用於關聯View和ViewModel。這種寫法比上一個例子感覺優雅很多。

啟動程式碼

介面Title正常顯示在ViewModel中設置的Title屬性為Custom ViewModel Application;

總結:在App.cs中重寫ConfigureViewModelLocator()方法,使用ViewModelLocationProvider.Register自定義關聯View和ViewModel的關係;

分析11-CustomRegistrations示例

工程引用Prism.Unity包;App.xaml繼承自PrismApplication;App.cs重寫CreateShell()設置啟動窗體;

重點在MainWindow.xaml和MainWindowViewModel.cs

前面的示例學習我們知道了,Views和ViewModels文件夾內的同名View和ViewModel會在Prism下自動關聯,不需要我們在ConfigureViewModelLocator()中從新綁定關係;也不需要寫DataContent。

1、MainWindow.xaml

添加命名空間 xmlns:prism=”//pismlibrary.com/

添加附加依賴項屬性:prism:ViewModelLocator.AutoWireViewModel=True

<Window x:Class="UsingDelegateCommands.Views.MainWindow"
        xmlns="//schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="//schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="//prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="Using DelegateCommand" Width="350" Height="275">
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <CheckBox IsChecked="{Binding IsEnabled}" Content="Can Execute Command" Margin="10"/>
        <Button Command="{Binding ExecuteDelegateCommand}" Content="DelegateCommand" Margin="10"/>
        <Button Command="{Binding DelegateCommandObservesProperty}" Content="DelegateCommand ObservesProperty" Margin="10"/>
        <Button Command="{Binding DelegateCommandObservesCanExecute}" Content="DelegateCommand ObservesCanExecute" Margin="10"/>
        <Button Command="{Binding ExecuteGenericDelegateCommand}" CommandParameter="Passed Parameter" Content="DelegateCommand Generic" Margin="10"/>
        <TextBlock Text="{Binding UpdateText}" Margin="10" FontSize="22"/>
    </StackPanel>
</Window>

同時打開ViewModels下的MainWindowViewModel.cs,左右分屏對照著看,MainWindowViewModel繼承自BindableBase;

在Xaml中CheckBox設置了IsChecked屬性綁定了ViewModel下的IsEnabled屬性,打開ViewModel下的IsEnabled屬性發現在Set時不僅有SetProperty用於屬性通知,還有一個ExecuteDelegateCommand.RaiseCanExecuteChanged()方法,暫且不管這裡再幹啥,我們繼續向下看。因為這裡有4種做一件事情的寫法,我們可以了解四種,但是用一種就行;

Xaml中4個Button分別綁定了ExecuteDelegateCommand、DelegateCommandObservesProperty、DelegateCommandObservesCanExecute、ExecuuteGenericDelegateCommand 命名綁定的同時傳入了CommandParameter參數為「Passed Parameter」。

有一個TextBlock 控制項, Text綁定為ViewModel中的UpdateText;

1.1、 我們來分析MainWindowViewModel.cs程式碼;

MainWindowViewModel繼承自BindableBase類,再set中使用SetProperty做屬性更改通知;

該類定義了IsEnable屬性和UpdateText屬性;和4個DelegateCommand、

在構造函數中初始化了4個DelegateCommand、在初始化過程中,除了最後一個ExecuteGenericDelegateCommand方法需要傳入一個參數,其他的都不需要,這4種寫法,都包含了2個邏輯,一個是執行Command、另外一個是是否可以執行,就是CanExecute用於返回當前指令是否可用;

當點擊Xaml的對應的Button的時候,就會觸發對應的Command、我喜歡用第一種寫法,拓展性比較好,也比較簡單明了;

 ExecuteDelegateCommand = new DelegateCommand(Execute, CanExecute);

需要注意的是下面這個

ExecuteGenericDelegateCommand = new DelegateCommand<string>(ExecuteGeneric).ObservesCanExecute(() => IsEnabled);

這個Command在初始化的時候,需要傳入一個參數,為字元串類型的。後面的都一樣。調用一個方法,驗證Command是否允許調用。

  public MainWindowViewModel()
        {
            ExecuteDelegateCommand = new DelegateCommand(Execute, CanExecute);

            DelegateCommandObservesProperty = new DelegateCommand(Execute, CanExecute).ObservesProperty(() => IsEnabled);

            DelegateCommandObservesCanExecute = new DelegateCommand(Execute).ObservesCanExecute(() => IsEnabled);

            ExecuteGenericDelegateCommand = new DelegateCommand<string>(ExecuteGeneric).ObservesCanExecute(() => IsEnabled);
        }

        private void Execute()
        {
            UpdateText = $"Updated: {DateTime.Now}";
        }

        private void ExecuteGeneric(string parameter)
        {
            UpdateText = parameter;
        }

        private bool CanExecute()
        {
            return IsEnabled;
        }

運行程式碼

顯示了標題為Using DelegateCommand的窗體;對照前面的調用關係,和屬性綁定關係,Prism下面如何在View和ViewModel中使用binding和Command,這裡就不講這2個細節了,第一個基礎系列WPF講過了。這裡只往返觀察一下View的程式碼和ViewModel的程式碼中Prism是如何使用的。嘗試點擊各個按鈕,看程式碼都運行到哪裡了。

總結:在Prism中使用binding還是比較方便的。保持Views和ViewModels的路徑關係,在MainWindow中設置Prism:ViewModelLocator.AutoWireViewModel=True,就自動關聯了2個路徑下的View和ViewModel的關係;

然後就可以使用Binding等一系列內容拉,不需要寫DataContent。Command和Binding的用於都一樣,ViewModel下需要繼承自BindableBase類,剩下的實現方法都一樣了。主要是Prism幫忙封裝了INotifyPropertyChanged 不需要自己在封裝了。

分析12-UsingCompositeCommands示例

這個示例是寫到現在終於遇到一個複雜的拉,包含3個工程我們來梳理一下引用關係

ModuleA工程引用Prism.Wpf包、UsingCompositeCommands.Core;

UsingCompositeCommands工程引用Prism.Unity包、ModuleA、UsingCompositeCommands.Core

UsingCompositeCommands.Core工程引用了Prism.Core

是不是有點多,這個程式碼慢慢來。因為這裡包含了Command的使用,涉及到了MVVM。如果不熟悉MVVM的話。這裡會有點懵,第一個WPF系列中這裡有講到,去翻我部落格就好拉。

1、分析引用關係最小的UsingCompositeCommands.Core工程

UsingCompositeCommands.Core引用了Prism.Core,核心就在Prism.Core。

1.1、新增ApplicationCommands.cs

新建InterFace介面 起名為IApplicationCommands、裡面定義了一個只讀的Prism.Commands下的CompositeCommand複合命令;

新建ApplicationCommands類繼承IApplicationCommands介面,並創建了屬性SaveCommand;

整個Core就結束了。這裡定義了介面IApplicationCommands和實現介面的ApplicationCommands;

具體怎麼實現的我們不去關注,我們就關注,UsingCompositeCommands.Core工程引用了Prism.Core

我們先學如何使用;只有這麼多程式碼。

using Prism.Commands;

namespace UsingCompositeCommands.Core
{
    public interface IApplicationCommands
    {
        CompositeCommand SaveCommand { get; }
    }

    public class ApplicationCommands : IApplicationCommands
    {
        private CompositeCommand _saveCommand = new CompositeCommand();
        public CompositeCommand SaveCommand
        {
            get { return _saveCommand; }
        }
    }
}

2、分析相對簡單邏輯內容較少的UsingCompositeCommands主工程;

因為UsingCompositeCommands.Core寫的內容注意再主工程UsingCompositeCommands中使用了。

先講這個,邏輯可以關聯上,最後講XAML上綁定的Commands;

UsingCompositeCommands引用了Prism.Unity包;

項目ModuleA;

項目UsingCompositeCommands.Core;

2.1、App.Xaml

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

修改Application為PrismApplication

去掉StartupUri屬性

2.2、App.cs

重寫CreateShell()方法,解析返回啟動介面

重寫ConfigureModuleCatalog()

通過程式碼載入ModuleA工程下的ModuleAModule,哈哈,上一篇再學習Module載入時,我也喜歡並且推薦的這種寫法啊。在這裡AddModule之後會走ModuleA對應的Module的OnInitialized方法初始化。

重寫RegisterTypes()

這個方法我們前面加上07的五個例子,一共15個例子我們都在重寫RegisterTypes()方法,又不知道幹啥用。又不能去掉,就報錯。這裡終於知道了。註冊單例,使用傳入的ContainerRegistry註冊RegisterSingleton介面和關聯對應的類。通過這個方法,在主工程中就可以使用IApplicationCommands介面下的類了。

2.3Views下的MainWindow.xaml

在MainWindow.xaml下添加了命名空間xmlns:prism=”//prismlibrary.com/

添加了附加依賴屬性prism:ViewModelLocator.AutoWireViewModel=true

Title綁定了{Title}

添加了資源並綁定了所有的TabItem中Header值綁定為Title

介面顯示中放置了一個TabControl控制項,設置了RegionName為ContentRegion和Button,Button綁定了一個Command,注意,這裡有個誤導人的地方,當我們看到這個程式碼的時候我們是不是就以為這是下面Core中的ApplicationCommands了? 不是的,XAML和ViewModel才有直接的綁定關係,這個SaveCommand要在ViewModel中去找。前面的App.cs中是註冊單例的ApplicationCommands;cs文件中無新增

  <Button Content="Save" Margin="10" Command="{Binding ApplicationCommands.SaveCommand}"/>

2.4ViewModels下的MainWindowViewModel.cs

MainWindowViewModel繼承自BindableBase;

設置了Title,和ApplicationCommands屬性

同時在ViewModel的構造函數中,依據傳入的初始化了ApplicationCommands對象;

這個工程中就沒有額外的程式碼了。但是大家發現一件事情了嘛,IApplicationCommands介面是有實現的SaveCommand方法的,但是這2個地方都沒有實現。雖然在主工程的Button下也使用了SaveCommand。我們繼續分析

3、分析ModuleA工程

ModuleA工程添加了Prism.Wpf

引用了UsingCompositeCommands.Core工程;

3.1、ModuleAModule.cs

繼承自IModule,重寫了OnInitialized()方法

我們找到MainWindow中ContentRegion的控制項;是一個TabControl

這裡是解析了3次TabView。使用SetTitle()方法獲取View的TabViewModel並設置Title,然後添加到Region中。

3.2、Views下TabView.xaml

添加命名空間mlns:prism=”//prismlibrary.com/

添加附加依賴項屬性prism:ViewModelLocator.AutoWireViewModel=True

介面上用到的binding、Command都是上一篇的知識。沒有額外的這些就不分析了;

cs文件中無新增程式碼

3.3、ViewModels下的TabViewModel.cs

TabViewModel繼承自BindableBase。

其他的不講,就分析一下IApplicationCommands在TabViewModel中的使用

首先在構造函數中TabViewModel傳入了IApplicationCommands,

上面創建了一個欄位,接收這個applicationCommands,

使用_applicationCommands.SaveCommand調用RegisterCommand傳入一個註冊命令,關聯兩個Command。

這樣的話。就關聯了SaveCommand和UpdateCommand。在MainWindow.xaml中綁定的Commond的ApplicationSaveCommand就是在這裡關聯的UpdateCommand。因為ApplicationCommands是單例的,所以在ViewModel下的構造函數都可以請求帶這個對象。使用RegisterCommand關聯命令,使用UnregisterCommand取消關聯;

4、運行程式碼

介面是顯示一個TabControl包含3個子頁面,和一個Save控制項,點擊Save會更新時間;

總結:我們主要重點分析ApplicationCommands功能,在一個單獨的工程中引入了Prism.Code然後編寫了一個實現IApplication介面的類,並封裝了一個Command屬性。在主工程的App.cs中通過重寫RegisterTypes()來實現註冊單例的ApplicationCommands,然後再UI的ViewModel中保存ApplicationCommands,使用對應的ApplicationCommands時需要實現的對象中關聯對應的ApplicationCommands,比如ModuleA初始化的時候,添加了3個TabView,再每個TabView的ViewModel構造函數中都註冊了SaeCommand的關聯事件,因為是單例,每個又都註冊了所以在點擊最外層Save的時候,所有關聯Command都會被執行,這裡通過Prism.Code實現了IApplicationCommands的解耦。這裡如果用好了,程式碼會非常整潔。

下圖是整個程式碼的邏輯關係MainWindowViewModel->ModuleAmodule->TabViewModel

我創建了一個C#相關的交流群。用於分享學習資料和討論問題。歡迎有興趣的小夥伴:QQ群:542633085

今天受到了北京-關關的提醒,我添加了目錄功能,可以更加方便的閱讀拉。針對各種讀的不方便的內容,歡迎提意見。