WPF入门到跪下 第十一章 Prism(四)View与ViewModel的自动关联
View与ViewModel的自动关联
一、ViewModelLocator
在学习MvvmLight框架时,也使用了ViewModelLocator
类。但在MvvmLight框架中,ViewModelLocator
只是一个自定义类,与框架无关,目的就是初始化IOC容器。而在Prism框架中则不同,Prism框架内置了ViewModelLocator
类,并且可以帮助我们进行View
与ViewModel
层之间的绑定。
1、使用示例
先查看一下整个使用过程,再进行解析。
程序集中,创建Views文件夹、ViewModels文件夹,将MainWindow.xaml放入Views文件夹中,并在ViewModels文件夹中创建MainWindowViewModel类
注意,在移动MainWindow.xaml时,切记要修改命名空间(xaml文件中的x:Class
属性以及后台代码中的命名空间)
public class MainWindowViewModel:BindableBase
{
private int value;
public int Value
{
get { return value; }
set
{
SetProperty(ref this.value, value);
}
}
}
在MainWindow.xaml中进行Prism
命名空间的引入以及ViewModelLocator.AutoWireViewModel
属性的设置
需要注意,AutoWireViewModel
默认就是为True
,表示自动关联ViewModel,因此这个命名空间引入以及设置属性的步骤是可以省略的。
-
示例
<Window ...... xmlns:prism="http://prismlibrary.com/" prism:ViewModelLocator.AutoWireViewModel="True" ......> <Grid> <TextBlock Text="{Binding Value}"/> </Grid> </Window>
2、关联规则
通过**ViewModelLocator
**进行View与ViewModel层的自动关联,有以下几点规则:
- ViewModel(视图模型类)与View(视图类)位于同一个程序集中
- 默认情况下,视图模型类位于
.ViewModels
命名空间下,视图类位于.Views
命名空间下。可以理解为放置在项目目录的ViewModels
和Views
文件夹下。(试了一下,直接放在项目目录下或者ViewModel
和View
文件夹下也是可以的) - 视图模型类与视图类名称对应,并以
ViewModel
结尾。这里有一点需要注意的,如果视图类的名称本身就是以View
结尾的,例如StudentView,那么视图类名称中只要一个View
就可以了,也就是StudentViewModel而不是StudentViewViewModel。
个人建议是,将视图模型类统一放在ViewModels
文件夹中,视图类位于Views
文件夹中,方便管理。
二、个性化配置
1、关联规则配置
这里以PrismApplication
启动方式为例,在Prism框架中,会自动将View与ViewModel进行关联,其关联规则如上文所述。
默认关联过程大致如下:
- 规定视图层的类型必须放在
.Views
命名控件的子空间下,然后将命名空间中的Views
替换成ViewModels
,来获得对应的视图模型的所在命名空间,例如Schuyler.Views
->Schuyler.ViewModels
- 获得视图层的类类型后,检查类类型的全名是不是以View结尾,如果是则在尾部添加Model,否则则添加ViewModel,以此来获得视图层类类型所对应的视图模型层的类类型。
- 通过视图层类类型命名空间获取到视图模型层的类类型后,将该视图模型层的实例对象设置为对应视图层实例对象的
DataContext
。
根据上述关联过程,想要修改默认的关联规则,只需要在启动类(App
)中,重写PrismApplication
类的ConfigureViewModelLocator
方法,并在方法中通过ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver
方法来进行关联过程的修改即可。
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(Func<Type, Type> viewTypeToViewModelTypeResolver)
:设置默认的视图类型与视图模型类型的分析器。
viewTypeToViewModelTypeResolver
:接收一个Type
类型参数,并返回一个Type
类型的Func
委托。接收的Type
为视图类的类类型,而从哪个命名空间获取这个类类型应该是根据启动时设置的主窗口的所在命名空间来定下的。返回的Type
则是根据接收到的Type
参数的命名空间转化后获得的对应视图模型的类类型。
具体实现代码如下:
前提是使用PrismApplication
进行项目启动,实现方式可以翻看前文。
这里是将ViewTest作为存放View类型的文件夹、ViewModelTest则是用来存放ViewModel类型的文件夹。
-
示例
public partial class App : PrismApplication { protected override Window CreateShell() { return Container.Resolve<ViewTest.MainWindow>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { } protected override void ConfigureViewModelLocator() { base.ConfigureViewModelLocator(); ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(ViewTypeToViewModelTypeResolver); } private Type ViewTypeToViewModelTypeResolver(Type viewType) { var viewName = viewType.FullName; //获得视图模型的命名空间 var viewModelName = viewName.Replace(".ViewTest.", ".ViewModelTest."); //判断视图类是不是以Window结尾,是则去掉 if (viewModelName.EndsWith("Window")) { viewModelName = viewModelName.Substring(0, viewModelName.Length - 6); } //判断是不是以View结尾 if (viewModelName.EndsWith("View")) { viewModelName += "Model"; } else { viewModelName += "ViewModel"; } return Type.GetType(viewModelName); } }
2、独立关联规则配置
上面所说得关联规则配置指的是整个项目内都必须遵守的,而有些时候只希望配置某对View与ViewModel的关联规则,比如View与ViewModel可能不再一个程序集、不在指定目录、类型名字不匹配等。
此时则需要重写PrismApplication
类的ConfigureViewModelLocator
方法,并在方法中通过调用ViewModelLocationProvider.Register
方法来进行单独的配置,具体由如下四种配置方法:
-
示例
public partial class App : PrismApplication { protected override Window CreateShell() { //创建主窗口对象 return Container.Resolve<ViewTest.MainWindow>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { //这里进行IOC容器管理类型的注册 } protected override void ConfigureViewModelLocator() { base.ConfigureViewModelLocator(); //方式1:通过 类型名称/类型 //ViewModelLocationProvider.Register(typeof(ViewTest.MainWindow).ToString(), typeof(ViewModelTest.MainViewModel)); //方式2:通过 类型/工厂 //ViewModelLocationProvider.Register(typeof(ViewTest.MainWindow).ToString(), // ()=>Container.Resolve<ViewModelTest.MainViewModel>()); //方式3:通过 泛型/工厂 //ViewModelLocationProvider.Register<ViewTest.MainWindow>(() => Container.Resolve<ViewModelTest.MainViewModel>()); //方式4:通过 泛型 ViewModelLocationProvider.Register<ViewTest.MainWindow, ViewModelTest.MainViewModel>(); } }
设计时的DataContext设置
无依赖注入
DataContext
一般是在程序运行时进行关联的如果希望在进行视图设计时就可以将数据展示出来方便进行图形设计,可以通过<d:Window.DataContext>
进行设置
-
示例
public class MainWindowViewModel { public string TestData { get; set; } = "测试数据"; }
<Window x:Class="WpfApp1.Views.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:local="clr-namespace:WpfApp1" xmlns:viewModels ="clr-namespace:WpfApp1.ViewModels" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <d:Window.DataContext> <viewModels:MainWindowViewModel/> </d:Window.DataContext> <Grid> <TextBlock Text="{Binding TestData}"/> </Grid> </Window>
有依赖注入
在使用Prism框架下,视图模型很多时候都会通过IOC容器,在构造函数中注入对应的服务,这个时候要在视图中设置设计时的DataContext
,就需要借助ObjectDataProvider
-
示例
public class MainWindowViewModel { private IRegionManager _regionManager = null!; public MainWindowViewModel(IRegionManager regionManager) { _regionManager = regionManager; } ...... }
<Window ...... xmlns:prism="http://prismlibrary.com/" ......> <d:Window.DataContext> <ObjectDataProvider ObjectType="local:MainWindowViewModel"> <ObjectDataProvider.ConstructorParameters> <prism:RegionManager/> </ObjectDataProvider.ConstructorParameters> </ObjectDataProvider> </d:Window.DataContext> ...... </Window>