什么是ViewModelLocator?与DataTemplates相比,它有哪些优缺点?

120

有人能给我简要介绍一下什么是ViewModelLocator、它的工作原理以及与使用DataTemplates相比使用它的优缺点吗?

我尝试在Google上找到相关信息,但似乎有很多不同的实现方式,并没有明确列出它是什么以及使用它的优缺点。

3个回答

222

介绍

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

依赖注入的好处

主要的好处在于,容器可以在运行时配置,以指示如何解析我们从中请求的类型。这允许通过指示它在应用程序实际运行时使用的类型(Views和ViewModels)来解析类型,但在运行应用程序的单元测试时,通过不同的指示来进行更大的可测试性。在后一种情况下,应用程序甚至没有UI(它没有运行;只有测试在运行),因此容器将在应用程序运行时使用的“正常”类型的位置上解析模拟

依赖注入带来的问题

到目前为止,我们已经看到,DI方法通过在应用程序组件的创建上添加抽象层来使应用程序易于测试。这种方法存在一个问题:它与Microsoft Expression Blend等可视化设计工具不兼容

问题在于在正常应用程序运行和单元测试运行中,必须有人使用指令设置容器,并要求容器解析视图以便将ViewModel注入其中。然而,在设计时没有我们的代码运行。设计师试图使用反射创建我们的视图实例,这意味着:
- 如果视图构造函数需要ViewModel实例,则设计师将不能实例化视图——它会以某种受控的方式出错。 - 如果视图具有无参数构造函数,则将实例化视图,但其DataContext将为null,因此我们将在设计师中获得一个“空”视图,这并不是很有用。
引入ViewModelLocator ViewModelLocator是一种额外的抽象,使用如下:
- 视图本身作为其资源的一部分实例化ViewModelLocator,并将其DataContext databind到定位器的ViewModel属性。 - 定位器以某种方式检测是否处于设计模式。 - 如果不处于设计模式,则定位器从DI容器中解析出ViewModel并返回,如上所述。 - 如果处于设计模式,则定位器使用自己的逻辑返回一个固定的“虚拟”ViewModel(请记住:设计时没有容器!)。该ViewModel通常预先填充了虚拟数据。

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

概述

ViewModelLocator是一种习惯用语,它让你在MVVM应用程序中保持DI的好处,同时还允许你的代码与可视化设计师良好地协作。这有时被称为应用程序的“混合能力”(指Expression Blend)。

阅读以上内容后,可以查看一个实际的示例here

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


4
非常感谢您的赞扬。您希望我能进一步解释View及其资源,这里的资源是否指View的属性?请问是否有一个具体示例链接可以说明这种模式?+1代表了对一个很好的解释的赞同。在Android中,View是用户界面的基本构建块之一,并使用资源来定义其外观和行为。这些资源可以包括布局文件、字符串、颜色和尺寸等内容。当我们说View的资源时,通常是指与其外观和行为相关的资源,如布局文件xml、drawable图像、样式(style)、主题(theme)等。而View的属性则是指直接影响View的行为和外观的属性,如宽度(width)、高度(height)、背景颜色(background color)等。如果您想了解更多关于View及其资源的详细信息,可以参考官方文档,其中包含丰富的示例和代码片段,帮助您更深入地理解这种模式。 - Metro Smurf
1
@MetroSmurf:你的链接在摘要部分。 - Jon
1
谢谢。使用ViewModelLocator有什么限制吗?我对它引用静态资源的事实有些担忧 - ViewModel是否可以在运行时动态创建?连接一个ViewModelLocator需要很多额外的代码吗? - Rachel
2
非常误导性的答案。View Model Locator 的主要目的不是为设计师提供虚拟数据。你可以通过指定 d:DataContext="{d:DesignInstance MockViewModels:MockMainWindowModel, IsDesignTimeCreatable=True}" 轻松实现这一点。Locator 的目的是实际上在视图上启用 DI,因为 WPF 在提供 DI 方面非常糟糕。例如:您有一个打开某些对话框窗口的主窗口。要以通常的方式解决对话框窗口上的 DI,您需要将其作为依赖项传递给主窗口!这可以通过 View Locator 避免。 - hyankov
另请参阅此https://dev59.com/2l8e5IYBdhLWcg3wyM0r和此http://blog.qmatteoq.com/the-mvvm-pattern-dependency-injection/。 - hyankov
显示剩余3条评论

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

使用thisdummy有什么区别吗? - Sebastian Xawery Wiśniowiecki

9

我不理解为什么这个问题的其他答案会涉及到设计师。

视图模型定位器的目的是允许您的视图实例化它(是的,视图模型定位器=视图优先):

public void MyWindowViewModel(IService someService)
{
}

不仅仅是这样:

public void MyWindowViewModel()
{
}

通过声明这个:
DataContext="{Binding MainWindowModel, Source={StaticResource ViewModelLocator}}"

这里提到的ViewModelLocator是一个类,它引用了一个IoC容器来解决它所暴露的MainWindowModel属性。

它与为视图提供模拟视图模型无关。如果需要这样做,只需执行以下操作:

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

视图模型定位器是一个包装器,可以包装一些(任何)反转控制容器,例如Unity等。
参考:

视图模型定位器不需要容器,用户通过配置决定如何解析视图模型,您可以使用容器或自己创建一个类型。因此,您可以进行基于约定的视图模型定位,例如,而不是在某个容器中预先注册所有视图和视图模型。 - Chris Bordeman
当你说“我不明白其他答案......环绕Designer”时,你是正确的,但定位器的重点是从视图中移除任何关于视图模型如何创建的知识,使得视图独立于此实例化过程,而将其留给了定位器。 定位器将能够提供不同风味的视图模型,也许是通过插件添加的一些自定义视图模型,定位器将管理这些模型(以及特定于设计时间的模型)。 视图将干净地摆脱定位正确版本的视图模型的任何过程,这确实有利于SoC。 - mins

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接