关于wpf:ViewModelLocator是什么,与DataTemplates相比,它的优缺点是什么?

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答案的示例实现

我有一个视图模型定位器类。每个属性都将是我将在视图上分配的视图模型的实例。我可以使用DesignerProperties.GetIsInDesignMode检查代码是否在设计模式下运行。这使我可以在设计时使用模拟模型,并在运行应用程序时使用真实对象。

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);
    }
}

要使用它,我可以将定位器添加到App.xaml资源中:

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}}"

其中ViewModelLocator是类,它引用一个IoC,这就是它解决所公开的MainWindowModel属性的方式。

它与为您的视图提供Mock视图模型无关。 如果你想要的话,就去做

1
d:DataContext="{d:DesignInstance MockViewModels:MockMainWindowModel, IsDesignTimeCreatable=True}"

视图模型定位器是Control容器的某些(任何)反转容器的包装,例如Unity。

参考: