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与视图类型位于同一个程序集中
- ViewModel位于
.ViewModels
子命名空间中,测试后发现其实.ViewModel
子命名空间中也可以。 - View位于
.Views
子命名空间中,测试后发现其实.View
子命名空间中也可以。 - ViewModel名称与视图名称对应,以ViewModel结尾。这里有一点需要注意的,如果视图的名称本身就是以View结尾的,例如StudentView,那么ViewModel名称中只要一个View就可以了,也就是StudentViewModel。
二、个性化配置
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>();
}
}