什么是ViewModelLocator?与DataTemplates相比,它的优缺点是什么?


112

有人可以给我快速概述一下ViewModelLocator是什么,它如何工作以及与DataTemplates相比使用它的优点/缺点吗?

我曾尝试在Google上查找信息,但似乎有许多不同的实现方式,没有关于它的含义以及使用它的利弊的详尽列表。

Answers:


204

介绍

在MVVM中,通常的做法是通过从依赖项注入(DI)容器中解析视图来使视图找到其ViewModel 。当要求容器提供(解析)View类的实例时,这会自动发生。容器通过调用接受ViewModel参数的View的构造函数 ViewModel注入到View中。这种方案称为控制反转(IoC)。

DI的好处

这里的主要好处是可以在运行时配置容器,其中包含有关如何解析我们向其请求的类型的说明。通过指示它解析我们在应用程序实际运行时使用的类型(视图和ViewModels),从而提供了更高的可测试性,但在为应用程序运行单元测试时以不同的方式指示它。在后一种情况下,应用程序甚至没有UI(它没有在运行;只是测试而已),因此容器将解析模拟,以代替在应用程序运行时使用的“常规”类型。

DI带来的问题

到目前为止,我们已经看到,DI方法通过在创建应用程序组件的过程中添加抽象层,使应用程序易于测试。这种方法有一个问题:它不适用于Microsoft Expression Blend等视觉设计器

问题在于,在正常的应用程序运行和单元测试运行中,都必须设置容器以说明要解决的类型;另外,必须有人要求容器解析视图,以便可以将ViewModels注入其中。

但是,在设计时没有运行代码。设计器尝试使用反射来创建我们的View实例,这意味着:

  • 如果View构造函数需要一个ViewModel实例,那么设计人员将根本无法实例化该视图-它将以某种受控方式出错
  • 如果视图参数的构造函数的视图将被实例化,但其DataContextnull,所以我们会得到在设计一个‘空’的观点-这是不是很实用

输入ViewModelLocator

ViewModelLocator是这样使用的附加抽象:

  • View本身将ViewModelLocator实例化为其资源的一部分,并将其DataContext绑定到该定位器的ViewModel属性
  • 定位器以某种方式检测我们是否处于设计模式
  • 如果不在设计模式下,则定位器返回一个从DI容器解析的ViewModel,如上所述
  • 如果处于设计模式,则定位器使用其自身的逻辑返回固定的“虚拟” ViewModel(请记住:设计时没有容器!);此ViewModel通常预填充了伪数据

当然,这意味着View必须具有一个无参数的构造函数(否则设计器将无法实例化它)。

摘要

ViewModelLocator是一个惯用语,可让您在MVVM应用程序中保留DI的优点,同时还使您的代码可与可视设计人员很好地配合使用。有时这称为应用程序的“可混合性”(指Expression Blend)。

消化完上述内容后,请参见此处的实际示例。

最后,使用数据模板不是使用ViewModelLocator的替代方法,而是使用UI的显式View / ViewModel对的替代方法。通常,您可能会发现不需要为ViewModel定义View,因为您可以改用数据模板。


4
+1是一个很好的解释。您能否进一步扩展视图及其资源?通过资源,您是指视图的属性吗?要么?您是否有指向此模式的具体示例的链接?
地铁蓝精灵

@MetroSmurf:您的链接位于“摘要”部分。
乔恩

1
谢谢。使用ViewModelLocator有任何限制吗?我对它引用静态资源感到担忧-可以在运行时动态创建ViewModels吗?并且有很多额外的代码涉及一个吗?
雷切尔

@Rachel:摘要中的同一链接应通过实际示例回答这些问题。
乔恩

2
极具误导性的答案。View Model Locator的主要目的不是向设计人员提供虚拟数据。您可以通过指定轻松地做到这一点d:DataContext="{d:DesignInstance MockViewModels:MockMainWindowModel, IsDesignTimeCreatable=True}"。定位器的目的是在视图上实际启用DI,因为WPF很难提供它。示例:您有一个主窗口,它打开了一些对话框窗口。要以通常的方式在对话框窗口上解决DI,您需要将其作为对主窗口的依赖项传递!使用“视图定位器”可以避免这种情况。
hyankov '16

10

@Jon答案的示例实现

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

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资源中:

xmlns:core="clr-namespace:MyViewModelLocatorNamespace"

<Application.Resources>
    <core:ViewModelLocator x:Key="ViewModelLocator" />
</Application.Resources>

然后将您的视图(例如:MainView.xaml)连接到您的视图模型:

<Window ...
  DataContext="{Binding Path=MainViewModel, Source={StaticResource ViewModelLocator}}">

使用this代替有什么区别dummy吗?
塞巴斯蒂安XaweryWiśniowiecki

5

我不明白为什么这个问题的其他答案围绕着Designer。

View Model Locator的目的是允许您的View实例化它(是的,View Model Locator = View First):

public void MyWindowViewModel(IService someService)
{
}

不仅仅是这样:

public void MyWindowViewModel()
{
}

通过声明:

DataContext="{Binding MainWindowModel, Source={StaticResource ViewModelLocator}}"

ViewModelLocatorclass 在哪里,它引用了一个IoC,这就是它解决MainWindowModel其公开的属性的方式。

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

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

View Model Locator是Control容器的某些(任何)Inversion的包装,例如Unity。

参考:


视图模型定位器不需要容器,用户可以决定如何通过配置来解析视图模型,您可以使用容器,也可以自己新建一个类型。因此,您可以执行基于约定的视图模型位置,例如,而不是在一个容器中预先注册所有视图和视图模型。
克里斯·博德曼

您说的很对,“ 我不明白为什么其他答案会绕在Designer上 +1”,但是定位器的目的是从视图中删除关于视图模型的任何知识创建后,使视图与该实例无关,将其留给定位器。定位器将能够提供不同类型的视图模型,也许通过定位器将管理的插件(以及设计时的特定插件)添加了一些自定义样式。视图将不包含任何查找视图模型的正确版本的过程,这对于SoC而言确实不错。
分钟
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.