What is a ViewModelLocator and what are its pros/cons compared to DataTemplates?
有人可以简要概述一下ViewModelLocator是什么,它如何工作以及与DataTemplates相比使用它的优点/缺点吗?
我曾尝试在Google上查找信息,但似乎有许多不同的实现方式,而没有关于它的含义以及使用它的利弊的详尽列表。
介绍
在MVVM中,通常的做法是通过从依赖项注入(DI)容器中解析视图来使视图找到其ViewModel。当要求容器提供(解析)View类的实例时,这会自动发生。容器通过调用接受ViewModel参数的View的构造函数将ViewModel注入到View中。这种方案称为控制反转(IoC)。
DI的好处
这样做的主要好处是可以在运行时配置容器,其中包含有关如何解析我们向其请求的类型的说明。通过指示它解析我们在应用程序实际运行时使用的类型(视图和ViewModels),从而提供了更高的可测试性,但是在为应用程序运行单元测试时以不同的方式指示它。在后一种情况下,应用程序甚至没有UI(它没有在运行;只是测试而已),因此容器将解析模拟,以代替应用程序运行时使用的"常规"类型。
DI带来的问题
到目前为止,我们已经看到,DI方法通过在创建应用程序组件的过程中添加抽象层,使应用程序易于测试。这种方法有一个问题:它不适用于Microsoft Expression Blend等视觉设计器。
问题是,在正常的应用程序运行和单元测试运行中,都必须设置容器以说明要解决的类型;此外,还必须有人要求容器解析视图,以便可以将ViewModels注入其中。
但是,在设计时没有运行代码。设计器尝试使用反射来创建我们的View实例,这意味着:
- 如果View构造函数需要一个ViewModel实例,那么设计人员将根本无法实例化该视图-它将以某种受控方式出错
-
如果View具有无参数的构造函数,则将实例化View,但其
DataContext 将为null ,因此我们将在设计器中获得"空"视图-这不是很有用
输入ViewModelLocator
ViewModelLocator是这样使用的附加抽象:
- View本身将ViewModelLocator实例化为其资源的一部分,并将其DataContext绑定到该定位器的ViewModel属性
- 定位器以某种方式检测我们是否处于设计模式
- 如果不在设计模式下,则定位器返回从DI容器解析的ViewModel,如上所述
- 如果处于设计模式,则定位器使用其自身的逻辑返回固定的"虚拟" ViewModel(请记住:设计时没有容器!);此ViewModel通常预填充了伪数据
当然,这意味着View必须具有一个无参数的构造函数(否则设计器将无法实例化它)。
摘要
ViewModelLocator是一个惯用语,可让您在MVVM应用程序中保留DI的优点,同时还使您的代码可与可视设计人员很好地配合使用。这有时被称为应用程序的"可混合性"(指Expression Blend)。
消化完上述内容后,请参见此处的实际示例。
最后,使用数据模板不是使用ViewModelLocator的替代方法,而是使用UI的显式View / ViewModel对的替代方法。通常,您可能会发现不需要为ViewModel定义View,因为您可以改用数据模板。
@Jon答案的示例实现
我有一个视图模型定位器类。每个属性都将是我将在视图上分配的视图模型的实例。我可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class ViewModelLocator { private DependencyObject dummy = new DependencyObject(); public IMainViewModel MainViewModel { get { if (IsInDesignMode()) { return new MockMainViewModel(); } return MyIoC.Container.GetExportedValue<IMainViewModel>(); } } // returns true if editing .xaml file in VS for example private bool IsInDesignMode() { return DesignerProperties.GetIsInDesignMode(dummy); } } |
要使用它,我可以将定位器添加到
1 2 3 4 5 | xmlns:core="clr-namespace:MyViewModelLocatorNamespace" <Application.Resources> <core:ViewModelLocator x:Key="ViewModelLocator" /> </Application.Resources> |
然后将您的视图(例如:MainView.xaml)连接到您的视图模型:
1 2 | <Window ... DataContext="{Binding Path=MainViewModel, Source={StaticResource ViewModelLocator}}"> |
我不明白为什么这个问题的其他答案围绕着Designer。
View Model Locator的目的是允许您的View实例化它(是的,View Model Locator = View First):
1 2 3 | public void MyWindowViewModel(IService someService) { } |
而不只是这个:
1 2 3 | public void MyWindowViewModel() { } |
通过声明:
1 | DataContext="{Binding MainWindowModel, Source={StaticResource ViewModelLocator}}" |
其中
它与为您的视图提供Mock视图模型无关。 如果你想要的话,就去做
1 | d:DataContext="{d:DesignInstance MockViewModels:MockMainWindowModel, IsDesignTimeCreatable=True}" |
视图模型定位器是Control容器的某些(任何)反转容器的包装,例如Unity。
参考:
- 如何在WPF / MVVM应用程序中处理依赖项注入
-