WPF 中的模型-视图-视图模型






3.74/5 (15投票s)
一篇关于创建 WPF 应用程序的文章,遵循 Model-View-ViewModel 模式

引言
Model-View-ViewModel 模式(MVC 的一种变体)提供了一种开发可测试图形应用程序的良好方式。Windows Presentation Foundation 的多项功能,例如数据绑定和命令架构,极大地有助于遵循此模式。不幸的是,一旦您开始在实际应用程序中遵循此模式,您就会遇到各种使 Model、View 和 ViewModel 的严格分离变得非常难以维护的问题。本文实现了一个功能齐全的应用程序的基础,阐述了可用于维持 M-V-VM 模式所要求的关注点严格分离的各种技术。
背景
我尝试遵循行为驱动开发 (Behavior-driven Development) 方法论来实现这个应用程序。我通过包含完整的版本历史记录(以 Bazaar 存储库的形式)来捕获这个尝试。Bazaar 是一个分布式版本控制系统,因此,整个存储库都包含在可下载的源代码存档中。您可以查看开发的每一个步骤,包括我在编码时犯下的所有各种错误。
尽管我尝试遵循 BDD,但不要期望这里有生产级别的代码。首先,我对这种方法论还不熟悉,您可以肯定我并没有像一个人应该做的那样去遵循它。其次,尽管该项目包含一个相当完整的示例应用程序,但它只是一个示例。我专注于实现一个遵循 M-V-VM 的 WPF 应用程序所需的必要事物,而不是使应用程序达到生产质量所需的东西。这只是一个起点。
另外,请注意,单元测试代码使用了大量自定义代码。这是这项工作的一个重要组成部分,所以我将其分离到一个单独的文章中:《Visual Studio Unit Testing Extensions》。
Using the Code
这个项目很多代码的“核心”位于 ViewModel
类中。这个类既是一个 MarkupExtension
类,也提供了一些附加属性。为了将您的 ViewModel 与您的 View 相关联,您像这样使用 ViewModel
类:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:CodeProject.Windows.Markup;assembly=CodeProjectCore"
xmlns:vm="clr-namespace:TaskList.ViewModel;assembly=TaskList.ViewModel"
c:ViewModel.Instance="{c:ViewModel {x:Type vm:MainWindowViewModel}}">
分配 ViewModel.Instance
附加属性会做几件事:
- 将属性分配给一个对象的实例。请注意,此属性会被子元素继承。
- 将
DataContext
属性分配给同一个实例。 - 将
ViewModel.Commands
属性分配给空字符串。
最后一个要点值得进一步解释。ViewModel.Commands
附加属性用于为 ViewModel 中的命令处理程序创建 View 上的命令绑定。这实际上是实现一个简单而“干净”的方式来完成的最棘手的事情之一。我已经在我的博客上(查看存档中有几篇关于这个主题的文章)和其他人,例如Brownie,也写过这方面的内容。
当 ViewModel.Commands
附加到某个元素时,它会在 ViewModel.Instance
中查找一个类型为 CommandBindingCollection
,并带有 CommandSinkAttribute
和匹配的 KeyName
的属性。此集合中的所有 CommandBinding
实例都会添加到元素的 Commands
中。这个解决方案受到文章《Smart Routed Commands in WPF》的启发,尽管这里的目的和实现显然不同。
ViewModel 不需要继承任何基类或实现任何接口。ViewModel
标记扩展会尝试首先通过查找接受与元素兼容的单个参数的构造函数来实例化 ViewModel。您应该避免在这里使用与 View 的紧密耦合,而是使用元素可以实现的接口。如果 ViewModel 没有符合此处标准的构造函数,它将改用默认构造函数(如果存在)进行实例化。通常最好不要在这里创建任何耦合,但有些事情在 ViewModel 中确实无法做到,例如导航到另一个页面。特定于 View 的接口允许您将此类代码放在 View 中,同时尽可能保持最少的耦合。
可以使用类似的接口来处理 Application
。这使得 ViewModel 可以访问应用程序范围的设置和功能,同时又不紧密耦合到实际的 Application
。示例应用程序演示了 View 接口和应用程序接口的使用,包括如何使用实现这些接口的 Mock 对象来简化测试。
ViewModel 的目标是将尽可能多的 UI 状态放入其中。然后 View 将此状态绑定到必要的元素。经典的例子是在 View 中显示的集合的选择状态。问题在于,View 中的某些状态由只读属性给出,无法用于数据绑定。多个控件上的 SelectedItems
属性就是一个例子。为了在 ViewModel 中维护这种状态,必须找到一种独特的解决方案,将状态绑定到 View 中元素的只读属性。Selection
类提供了一个附加的 SelectedItems
属性,以说明实现此目的的一种方法。该附加属性负责监视元素和 ViewModel 上的状态,并使两者保持同步。
奖励:DataErrorInfo
这个类与本文的主要焦点 M-V-VM 关系不大,请将其视为奖励。这是基于 IDataErrorInfo
的验证框架的起点。它使用各种 ValidatorAttribute
类来指定属性应如何被验证。代码中仅包含 StringLengthValidatorAttribute
,但创建其他验证器应该很简单。Task
Model 类说明了用法,EditTaskPage 展示了如何在 WPF 中使用它进行验证。
历史
2007 年 12 月 16 日 - 发布初始文章。