这一篇是学习了前2篇RegionManager关联视图,和通过不同的方式加载Module示例之后的开始进入MVVM了。
从第08示例开始,进入了MVVM部分。
从08示例开始学习Prism下的MVVM思想
观察08-ViewModelLocator示例
08示例只有一个工程,添加了Prism.Unity的包
1、分析ViewModelLocator工程
1.1、App.xaml
添加了命名空间xmlns:prism="http://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="http://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="http://pismlibrary.com/"
添加附加依赖项属性:prism:ViewModelLocator.AutoWireViewModel=True
<Window x:Class="UsingDelegateCommands.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://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="http://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="http://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="http://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
今天受到了北京-关关的提醒,我添加了目录功能,可以更加方便的阅读拉。针对各种读的不方便的内容,欢迎提意见。