65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Visitor 模式在 WPF 中实现对话框的同时维护 MVVM 分层

starIconstarIconstarIconstarIconstarIcon

5.00/5 (7投票s)

2020年1月2日

CPOL

4分钟阅读

viewsIcon

11056

downloadIcon

311

一种简化方法,用于在 ViewModel 需要显示窗体对话框时维护 WPF MVVM 层。

引言

在显示对话框窗体时,保持 MVVM (Model-View-ViewModel) 的分层结构会很困难,因为 ViewModel 层中的命令处理程序需要某种方式访问对话框窗口,而又不破坏分层结构。ViewModel 不应该与任何 View(Windows UI)对象存在编译时依赖,包括包含对话框窗口的 View,而对话框窗口需要一个引用,以操作其将要操作的数据,而这些数据是直到运行时才知道的。本文介绍了一种使用修改后的访问者模式 (Visitor Pattern)(Gamma 等人,《设计模式》)和依赖注入 (DI) 来保持分层结构分离的简化方法。

背景

访问者模式将数据与其要执行的操作分离开。在这种情况下,对话框窗体需要更新或填充对象的字段。Visitor 通过拥有一个具有重载方法的类来实现这一点,每个方法接受一个特定对象类型(类)的参数。因此,当调用带有特定参数类型的“Visit”方法时,会自动选择正确的方法。这些 Visit 方法负责创建正确的对话框窗口,将参数对象指定为对话框的 DataContext,并显示对话框(这里我们假设是模态对话框)。Visitor 通过属性注入(ViewModel 对象有 public Visitor 属性字段来持有对 Visitor 的引用)在 MainWindow 加载事件处理程序中被注入到 ViewModel (VM) 对象中。我认为这比使用中介者 (mediator) 更简单,因为不需要事件在层之间传递。

此示例不需要依赖注入容器,尽管添加一个容器并不困难。它不引用 Prism Behaviors 或其他外部框架。可以将其添加到现有代码库中,而不会造成任何干扰。

Using the Code

ViewModel 使用 Relay Command (Hall,《Pro WPF and Silverlight MVVM》,Apress) 来提供命令行为。VM 对象在 XAML 中作为静态资源创建。MainWindowLoaded 事件处理程序检索这些对象,实例化 ViewVisitor 类,并通过设置其 Visitor 属性将其注入到 VM 对象中。主窗口的按钮在 XAML 中绑定到命令。当调用时,命令处理程序要么创建一个新的数据对象,要么检索当前选中的对象,然后调用 VisitorDynamicVisitor 方法,该方法调用与参数匹配的重载 Visit 方法。

本文的重点是 DialogVisitorViewModel 层定义了一个接口 IDialogVisitor,其中有一个方法 DynamicVisitViewModel 类包含对该接口类的公共引用。因此,ViewModel 类(ViewPersons, ViewVehicles)只对 ViewModelModel 层有编译时依赖。需要注意的是,该接口没有定义任何显示对话框窗口的方法。

 // The interface is defined in the ViewModel
public interface DialogVisitor
 {
    object DynamicVisit(Object data);
 }

View 层定义了一个派生的 DialogVisitor,它重写了 DynamicVisit 方法,并根据 Visit 方法的签名提供调用正确 Visit 方法的方法,并定义了 private Visit 方法。Visit 方法负责实例化并显示处理其参数中对象的对话框窗口。MainWindowloaded 事件处理程序实例化 DynamicVisit 类,并将其(通过属性注入)注入到 ViewModel 类中。

/// <summary>
/// Modified Visitor. Using Dynamic to simplify the pattern.
/// See "Albahari, C# 7.0 in a Nutshell"
/// Daniel Ziegelmiller, author
/// </summary>
public class DialogVisitor : ViewModel.IDialogVisitor
{
    /// <summary>
    /// The method which is called by ViewModel classes to instantiate and show the dialog 
    /// windows. By dynamic member resolution, the correct private Visit method will
    /// be invoked based on the method signature.
    /// </summary>
    /// <param name="data">The object which the dialog window
    /// will manipulate.</param>
    /// <returns>The object argument as modified.</returns>
public object DynamicVisit(Object data) => Visit((dynamic)data);

    // create overloaded Visit methods. The correct one will
    // be called based on the method signature, when the DynamicVisit delegate 
    // is invoked.
    //
    // This decouples the data (argument) from the action (dialog) performed
    // on it.

private Person Visit(Person p)
  {
    var dlg = new PersonDialog();
    dlg.DataContext = p;
    dlg.ShowDialog();
    return p;
  }

private Vehicle Visit(Vehicle v)
  {
    var dlg = new VehicleDialog();
    dlg.DataContext = v;
    dlg.ShowDialog();
    return v;
  }
}

Visitor 被注入到 ViewModel 类之后,会调用 DynamicVisit 方法来显示对话框,例如:

 public void NewPerson()
 {
    if (Visitor == null) return;

    Person p = new Person();
    Visitor.DynamicVisit(p);
    PersonList.Add(p);
 }
    

示例中的大部分代码都是支持和演示 Visitor 类的脚手架。数据参数可能包含控制对话框处理程序中更复杂行为的信息。DialogVisitor 可以轻松地扩展更多 Visit 方法,只要它们都基于参数类型具有不同的签名。

编译后,运行程序并单击“添加”按钮以打开对话框来创建一些数据行;选中一行,然后观察“更新”按钮会启用。单击“更新”按钮以打开包含行数据的对话框。修改它,当对话框关闭时,行数据将反映更新。

单元测试

此示例未演示单元测试。由于 ViewModel 层中没有对任何 View 或 UI 对象的依赖,可以通过实例化 DynamicVisitor 类的测试版本并将其注入到被测 ViewModel 类中来进行单元测试。

限制

一个 DynamicView 类每个对话框数据类型只能有一个方法。根据应用程序的复杂性,可能不止一个 DynamicView 类,并且可能具有包含多个参数的 Visit 方法。

此示例使用 MainWindowloaded 事件处理程序来创建 DynamicView 并将其注入到 ViewModel 类中,以便 ViewModel 类可以拥有无参数构造函数。可以将 ViewModel 类重构为接受 DynamicView 类作为构造函数参数,并使用 XAML 中的 ObjectDataProvider 机制来创建 DynamicViewViewModel 类,注入 DynamicView。这取决于个人喜好。在我看来,示例机制使得工作内容更清晰,并且类似于单元测试的设置方式。

历史

  • 2020年1月2日:初始版本
使用访问者模式在 WPF 中实现对话框时保持 MVVM 分层结构 - CodeProject - 代码之家
© . All rights reserved.