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

WPF 主从视图 MVVM 应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (61投票s)

2012年2月20日

CPOL

8分钟阅读

viewsIcon

187936

WPF 主从视图 MVVM 应用程序

强烈建议您通过查看源代码来逐步了解这些解释,这将帮助您获得更扎实的理解。您也可以在计算机上运行该应用程序。此项目的目标是通过一种非常简单的方法向您展示如何构建一个灵活且可扩展的应用程序。

这是一个使用 MVVM 模式在 UI 层实现的 WPF 主从视图应用程序。目前,让我们先看看应用程序的外观。

有一个 customer 列表,每个 customer 可以有多个 orders。当您单击一个 customer 时,它会显示属于该 customerorders 列表。

您可以通过单击按钮来添加、编辑或删除 customers。添加和编辑客户窗口使用相同的视图,因此它们看起来相同。删除 customer 将删除属于该 customer 的所有 orders

同样,您可以为 customer 添加、编辑或删除 order。添加和编辑订单窗口也使用相同的视图。有验证功能,以确保输入的订单信息是正确的。按钮仅在通过验证后才启用。

UI 层使用 WPF MVVM 模式构建。它向您展示了如何:

  • 设计 ViewViewModel
  • 处理用户交互
  • 执行验证
  • 使用 MVVM 模式和依赖注入的示例打开新窗口

视图和视图模型

下面是显示 ViewViewModel 主要类的图表:

View 中,我们有:

  • MainWindow -- 应用程序启动时的主窗口
  • CustomerListView -- 列出所有 customers,并在单击“添加客户”或“编辑客户”时打开 CustomerView
    • CustomerView -- 用于添加或编辑客户的视图
  • OrderListView -- 列出所选 customer 的所有订单,并在单击“添加订单”或“编辑订单”时打开 OrderView
    • OrderView -- 用于添加或编辑订单的视图

ViewModel 类旨在绑定到视图。这些类并非旨在复制已经完成的域模型(在业务层中)。

MainWindow 通过将应用程序的 DataContext 分配给 CustomerListViewModel 单例 来初始化它。

MainWindow 的 XAML 中,它通过使用 DataTemplates 指定要使用的视图(CustomerListViewOrderListView)。

请注意,CustomerListView 绑定到 CustomerListViewModel,而 OrderListView 绑定到 CustomerListViewModelSelectedCustomer 属性,该属性是 CustomerViewModel,它引用属于该客户的订单。以下是 CustomerListView 的 UI:

CustomerListView 绑定到 CustomerListViewModel,我们指定了我们希望每个 UI 控件绑定的 ViewModel 的属性。

请注意,GridViewColumnCellTemplate 定义了编辑和删除按钮。但更重要的是,ListViewSelectedItem 绑定到 ViewModelSelectedCustomer,当用户单击 customer 时,它会显示属于该 customer 的订单列表。这是因为 OrderListView 也绑定到 ViewModelSelectedCustomer

以下是 OrderListView 的 XAML:

该按钮检查 DataContext 是否为 null(当没有 customer 被选中时)。如果为 null,则“添加订单”按钮被禁用。ListView 绑定到 CustomerViewModelOrders 属性以显示订单列表。

单击“添加客户”或“编辑客户”按钮时会打开 CustomerView 窗口。以下是 CustomerView 的 UI,它可以是“添加客户”或“编辑客户”窗口:

窗口通过查看 CustomerViewModelMode 属性来确定它是“添加”还是“编辑”窗口。Mode 可以是“添加”或“编辑”。

CustomerView 通过查看 Mode 属性显示不同的文本。

单击窗口上的“添加”或“保存”按钮将调用 CustomerViewModelUpdate() 方法,该方法根据 Mode 属性确定是添加还是编辑 customer

同样的概念也适用于 OrderView

执行命令

为了执行命令,我们编写了一个 CommandBase 基类,可以在 ViewModel 中使用。基类仅传递在执行命令时运行的委托。

然后,在 ViewModel 中,我们声明了视图可以绑定的命令。例如,在 CustomerViewModel 中,我们定义了删除 customer 的命令。

调用时将运行 Delete() 方法。然后,在 CustomerListView 中,我们指定了单击删除按钮时要执行的命令。

所有命令都以这种方式设置,以方便将命令绑定到 ViewModel

设置自动 UI 更新

接下来,我们需要确保当 ViewModel 的属性发生更改时,整个 UI 都会更新。这是通过包含一个 ViewModelBase 类来实现的,所有 ViewModel 都继承自该类。

例如,CustomerViewModel 继承自 ViewModelBase 类。

CustomerViewModelFirstName 属性更改时(例如在编辑 customer 时),整个 UI 都应反映此更改。在 ViewModel 的属性中,我们只需调用 OnPropertyChanged 方法并传入属性名称,WPF 将处理其余部分。

UI 更新后的撤销

当用户取消更改时,您会遇到数据绑定问题。例如,用户可能在编辑客户窗口中更改客户的姓氏,这将更新整个 UI...

...然后决定通过按“取消”按钮来取消。使用默认的双向数据绑定,即使用户取消了更改,UI 也会保留更改后的值。

第一种想法可能是使用单向绑定,仅在用户单击“保存”按钮时才提交更改。这可以工作,但您会遇到更多问题:

  • 您需要将多个值传递给保存命令,这会弄乱 XAML,您还需要将从命令传递的多值从 object 类型列表进行强制类型转换。
  • 您将无法使用 WPF 提供的需要数据绑定的验证。

解决方法是允许 WPF 继续绑定,并在单击“取消”按钮时执行回滚。我们使用构造函数和 ViewModelUpdate() 方法中的 MemberwiseClone() 存储原始值(尽管您也可以编写自己的深拷贝方法,如果您有引用类型字段)。

然后 ViewModel 中的 CancelCommand 将只调用 Undo() 并回滚到原始值,整个 UI 将被更新。

相同的方法也适用于编辑订单屏幕。

验证

以下是 OrderView,其中“保存”按钮因验证消息而被禁用:

按钮的禁用是基于 OrderView.xamltextboxes 的 Validation.HasError 属性。

 

为了在文本框上执行验证,我们设置了 ValidatesOnDataErrorsNotifyOnValidationErrorUpdateSourceTrigger 属性。

UpdateSourceTriggerPropertyChanged 值意味着在 textbox 中的每次击键都会执行验证。我们在 ValidationStyle.xaml 中定义了在发生错误时显示的 UI。

AdornedElementPlaceholder 显示正在验证的控件,而 ErrorContent 是验证消息字符串。

验证检查是在 ViewModel 中通过实现 IDataErrorInfo 接口来完成的。

IDataErrorInfo 接口的实现中,我们根据条件指定要显示的错误消息。

请注意,我们将 OrderViewModelQuantity 属性设置为 string 而不是 int,这样,当输入非整数时,我们可以显示更友好的用户消息,而不是默认的类型转换消息,这对普通用户来说并不直观。

打开新窗口

有两种方法可以打开新窗口:

  • 使用代码隐藏
  • 使用通过依赖注入的 ViewModel

使用代码隐藏可以减少代码量,而使用 ViewModel 则可以通过使用成熟的框架来抽象出打开窗口的细节。

使用代码隐藏打开窗口

我们通过将 DataContext 分配给新窗口并打开它来使用代码隐藏打开添加/编辑订单窗口。

 

使用通过依赖注入的 ViewModel 打开窗口

依赖注入意味着您预先将一个类型注册到一个 interface,当请求 interface 时,预先注册的类型会自动为该 interface 创建。这在打开添加/编辑 Customer 窗口时有所体现。在 CustomerViewModel 中,我们定义了打开编辑客户窗口的命令,该命令打开一个 IModalDialog

请注意,ViewModel 不知道要打开哪个视图。它只需要获取对预先注册的 IModalDialog interface 的引用,并调用其方法来打开窗口。

视图到 IModalDialog 的预注册是在应用程序启动时的 BootStrapper 中完成的。在这种情况下,当请求 interface 时,我们使用 CustomerViewDialog 作为 IModalDialog

另外请注意,我们使用 UnityServiceLocator 来管理注册,它实际上只是使用了 Microsoft Enterprise Library 的 Unity Framework(您也可以使用其他框架)。

我们希望您觉得这个项目很有用。尽管还有很多事情可以做,但这应该能为您构建一个灵活的主从视图应用程序提供一个良好的起点。

© . All rights reserved.