目录
静态类ViewModelLocationProvider维护一张View.GetType().ToString() --> ViewModel实例的映射表,以及一张View.GetType().ToString() --> ViewModel Type的映射表,以及根据View的FullName推算出ViewModel Type 的FullName的策略,还有一个根据ViewModel的Type构造出1个ViewModel实例的策略,最终得到适配View的ViewModel实例赋值给其DataContext.
快速实践
在Application的OnStartup(StartupEventArgs startupEventArgs)中
ViewModelLocationProvider.SetDefaultViewModelFactory((vmType) =>
{
// 返回一个ViewModel实例。一般会从DI容器取出ViewModel实例。实现这个则下面的不用实现了。
});
ViewModelLocationProvider.SetDefaultViewModelFactory((view, vmType) =>
{
// 返回一个ViewModel实例。一般会从DI容器取出ViewModel实例。实现这个则上面的不用实现了。
});
ViewModelLocationProvider.Register<View>(()=>new ViewModel()); // 注册工厂委托到字典
ViewModelLocationProvider.Register<View,ViewModelType>(); // 注册类型到字典,不"劳烦"ViewTypeToViewModelTypeResolver解析。
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
{
// 返回1个ViewModel Type。
// 可以覆盖Prism 默认的确定ViewModel Type的策略。
// 一般不会选择重新定制此策略,而是使用Prism默认的策略
});
在每个View中的XAML中
<Window x:Class="Demo.Views.MainWindow"
...
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
或在每个View的CS文件合适的位置(一般是构造函数或Load函数)中
ViewModelLocator.SetAutoWireViewModel(view,true);
View.DataContext = GetViewModelInstance()的时机
View的附加属性ViewModelLocator.AutoWireViewModel设置成True时,会触发ViewModelLocationProvider去为View寻找合适的ViewModel实例。
若View要利用Prism自动定位ViewModel功能,需添加附加属性ViewModelLocator.AutoWireViewModel并设置成True。
<Window x:Class="Demo.Views.MainWindow"
...
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
附加属性的定义细节。被定义在ViewModelLocator类,重点是其元数据中的回调函数PropertyChangedCallback,它会在AutoWireViewModelProperty=True后被触发完成ViewModel的构建和DataContext的赋值。
public static class ViewModelLocator
{
public static DependencyProperty AutoWireViewModelProperty = DependencyProperty.RegisterAttached("AutoWireViewModel", typeof(bool?), typeof(ViewModelLocator), new PropertyMetadata(defaultValue: null, propertyChangedCallback: AutoWireViewModelChanged));
}
分析ViewModelLocator的方法AutoWireViewModelChanged
private static void AutoWireViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
_WINUI
if (!DesignerProperties.GetIsInDesignMode(d))
{
var value = (bool?)e.NewValue;
if (value.HasValue && value.Value) // View的依赖属性AutoWireViewModelProperty被赋值成True时才会真的去寻找合适ViewModel实例作为其DataContext。View不添加附加属性或添加附加属性但初值是False,Prism都不会为View自动构建ViewModel.
{
ViewModelLocationProvider.AutoWireViewModelChanged(d, Bind); // d是View实例,根据View.GetType().ToString()和View的FullName构建出适配的ViewModel,然后Bind方法完成View.DataContext=ViewModel.
}
}
}
static void Bind(object view, object viewModel)
{
if (view is FrameworkElement element) // 只要是FrameworkElement,就可以做View。(WPF元素继承链从FrameworkElemt开始才具有DataContext属性)。
element.DataContext = viewModel;
}
View的附加属性ViewModelLocator.AutoWireViewModel设置成True时,才会触发构建ViewModel实例并完成View.DataContetx=ViewModel。按照习惯,通常在XAML中添加prism:ViewModelLocator.AutoWireViewModel="True"
,所以一般是在View的构造函数中完成DataContext的赋值的。我们完全可以挪到View的Load函数中:ViewModelLocator.SetAutoWireViewModel(view,true);
自动为View构建合适的ViewModel的策略
public static void AutoWireViewModelChanged(object view, Action<object, object> setDataContextCallback)
{
// Try mappings first
object viewModel = GetViewModelForView(view); // 检查委托工厂
// try to use ViewModel type
if (viewModel == null)
{
//check type mappings
var viewModelType = GetViewModelTypeForView(view.GetType()); // 检查ViewModel Type 缓存
// fallback to convention based
if (viewModelType == null)
viewModelType = _defaultViewTypeToViewModelTypeResolver(view.GetType()); // 利用定位功能
if (viewModelType == null)
return;
// 实例化ViewModel
viewModel = _defaultViewModelFactoryWithViewParameter != null ? _defaultViewModelFactoryWithViewParameter(view, viewModelType) : _defaultViewModelFactory(viewModelType);
}
// 完成DataContext = ViewModel
setDataContextCallback(view, viewModel);
}
以View.GetType().ToString()为Key,在字典(Dictionary<string, Func<object>>())中寻找返回ViewModel实例的Func,拿到ViewModel实例。
上一步成功,直接View.DataContext = ViewModel. 上一步失败,下一步先确定View所需要的ViewModel的类型,拿到类型再实例化。
以View.GetType().ToString()为Key,去字典(Dictionary<string, Type>())中找到ViewModel的Type.
上一步成功,直接跳到根据Type实例化ViewModel的步骤,否则,利用Prism确认ViewModel类型规则继续确认ViewModel的Type.
如果默认规则成功确定Type,则实例化完成Wire,否则,因无法得到ViewModel实例导致一张DataContext为NULL的View诞生。(并不会抛出异常提醒,这是合理的,可能有的View没遵守MVVM开发,后台逻辑放到View的XAML和CS文件中了)。
Prism自动确认ViewModel Type的规则
static Func<Type, Type> _defaultViewTypeToViewModelTypeResolver =
viewType =>
{
var viewName = viewType.FullName;
viewName = viewName.Replace(".Views.", ".ViewModels.");
var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
var suffix = viewName.EndsWith("View") ? "Model" : "ViewModel";
var viewModelName = String.Format(CultureInfo.InvariantCulture, "{0}{1}, {2}", viewName, suffix, viewAssemblyName);
return Type.GetType(viewModelName);
};
先确定ViewModel的FullName字符串,即Namespace和Name.
ViewModel的名称
- View如果以View结尾,追加Model,否则追加ViewModel。
例:
EmployeeList,则ViewModel是EmployeeListViewModel
EmployeeListView,则ViewModel是EmployeeListViewModel
ViewModel命名空间
将View命名空间中的Views替换成ViewModels即可。如果View的命名空间没有Views字眼,那么ViewModel的命名空间和View的一模一样。
例:
A.B.Views.EmployeeList A.B.ViewModels.EmployeeListViewModel
A.B.EmployeeListView A.B.EmployeeListViewModel
- 在View所在程序集中寻找FullName是上一步得到的FullName字符串的Type。存在这样的Type,则实例化作为DataContext,否则View的DataContext为NULL。
小结
View的名称可以带View也可以不带View,但ViewModel一定要以ViewModel结尾。
Prism默认的确认ViewModel策略与文件夹无关,只与命名空间有关,只是命名空间受文件夹影响而已。
实例化策略
viewModel = _defaultViewModelFactoryWithViewParameter != null ? _defaultViewModelFactoryWithViewParameter(view, viewModelType) : _defaultViewModelFactory(viewModelType);
static Func<Type, object> _defaultViewModelFactory = type => Activator.CreateInstance(type);
static Func<object, Type, object> _defaultViewModelFactoryWithViewParameter;
得到ViewModel的Type,下一步就是构造ViewModel实例。上面代码是默认实现。
defaultViewModelFactoryWithViewParameter始终是NULL,所以一直采用defaultViewModelFactory构造。但defaultViewModelFactory只是利用反射调用ViewModel的无参构造函数实例化,缺点就是如果ViewModel的不含无参构造函数,或者想利用有参构造函数实例化,又或者ViewModel的构造函数参数有接口类型想借助依赖注入容器实例化,这些_defaultViewModelFactory都做不到。
所以定制实例化策略是很经常很有必要的基操。
ViewModelLocationProvider.SetDefaultViewModelFactory(viewModelType) =>
{
return viewModel instance;
});
ViewModelLocationProvider.SetDefaultViewModelFactory((view, viewModelType) =>
{
switch (view)
{
case Window window:
//your logic
break;
case UserControl userControl:
//your logic
break;
}
return viewModel instance;
}
上述代码完成对委托类型的静态字段defaultViewModelFactoryWithViewParameter和defaultViewModelFactory的赋值。实现其中1个就可以了,如果2个都实现,Prism只会优先使用defaultViewModelFactoryWithViewParameter。2者之间的差别就是前者的参数多个View,如果需要的话,在内部可利用View做一些额外的判断,仅此而已。
依赖注入无关性
回顾一下Prism自动定位ViewModel的全貌,只与两个静态类ViewModelLocator和ViewModelLocationProvider有关,前者提供附加属性用于触发定位机制和负责将DataContext=ViewModel实例,后者负责存储View和ViewModel的映射关系以及实例化ViewModel,全程与依赖注入无关。
所以,即使当前项目已经基于其他MVVM开发,也能单独使用Prism的自动定位功能。
Prism的定位功能可以分成3个部分,查找工厂委托字典、定位ViewModel Type、实例化ViewModel。我们甚至可以将所有View的ViewModel构造方法存储到字典中,根本不会使用Prism定位功能的后半部分:智能确认ViewModel Type和实例化策略。
要不要在项目中使用自动定位呢? 建议用,可以强制团队成员规范项目文件夹结构。
开发规范
-
尽量所有的View和其ViewModel都遵守命名规约
View和ViewModel的命名空间完全相同,除了Views和ViewModels字符串不匹配是允许的,且ViewModel的命名空间不能包含.Views.;View的名称无要求,但习惯是以View结尾,但ViewModel名称必须以ViewModel结尾。
假设
View命名空间: A.B.C.D.E
ViewModel命名空间: a.b.c.d.e
必须满足:View点分成5段,ViewModel也必须要点分成5段。对应序号的段相互比较(A和a比较,B和b比较...),必须完全相同,除非View段是Views,ViewModel段是ViewModels也被认为段相同,其他情况一律是段不同匹配失败!
下面的例子是合规的,即使Views和ViewModels不在命名空间的末尾。
View : A.B.Views.C.EmployeeListView.xaml
ViewModel : A.B.ViewModels.C.EmployeeListViewModel.cs
下面的例子也是合规的,即使View文件不以View结尾。
View : A.B.EmployeeList.xaml
ViewModel : A.B.EmployeeListViewModel.cs
- 所有的View开启自动确认ViewModel功能,不遵守命名规约的可以将其ViewModel的Type添加到字典,不让Prism费劲去分析FullName。
ViewModelLocationProvider.Register(typeof(MainWindow).ToString(), typeof(CustomViewModel));
ViewModelLocationProvider.Register<MainWindow, CustomViewModel>();
不遵守命名规约或虽然遵守但是ViewModel的实例化比较异类特殊,也可以将ViewModel的工厂委托添加到字典。
ViewModelLocationProvider.Register(typeof(MainWindow).ToString(), () => Container.Resolve<CustomViewModel>());
ViewModelLocationProvider.Register<MainWindow>(() => Container.Resolve<CustomViewModel>());
如果根据ViewModel Type进行实例化的defaultViewModelFactoryWithViewParameter和defaultViewModelFactory以及工厂委托使用依赖注入容器解析ViewModel的话,别忘了先注册ViewModel到容器。另外,记得工厂委托会优先于实例化策略来提供ViewModel实例。
标签:ViewModelLocationProvider,ViewModel,AutoWireViewModel,Prism,实例,Type,View From: https://www.cnblogs.com/LiuwayLi/p/17644714.html