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