WPF 主从视图 MVVM 应用程序
WPF 主从视图 MVVM 应用程序
强烈建议您通过查看源代码来逐步了解这些解释,这将帮助您获得更扎实的理解。您也可以在计算机上运行该应用程序。此项目的目标是通过一种非常简单的方法向您展示如何构建一个灵活且可扩展的应用程序。
这是一个使用 MVVM 模式在 UI 层实现的 WPF 主从视图应用程序。目前,让我们先看看应用程序的外观。
有一个 customer
列表,每个 customer
可以有多个 orders
。当您单击一个 customer
时,它会显示属于该 customer
的 orders
列表。
您可以通过单击按钮来添加、编辑或删除 customers
。添加和编辑客户窗口使用相同的视图,因此它们看起来相同。删除 customer
将删除属于该 customer
的所有 orders
。
同样,您可以为 customer
添加、编辑或删除 order
。添加和编辑订单窗口也使用相同的视图。有验证功能,以确保输入的订单信息是正确的。按钮仅在通过验证后才启用。
UI 层使用 WPF MVVM 模式构建。它向您展示了如何:
- 设计
View
和ViewModel
- 处理用户交互
- 执行验证
- 使用 MVVM 模式和依赖注入的示例打开新窗口
视图和视图模型
下面是显示 View
和 ViewModel
主要类的图表:
在 View
中,我们有:
MainWindow
-- 应用程序启动时的主窗口CustomerListView
-- 列出所有customers
,并在单击“添加客户”或“编辑客户”时打开CustomerView
CustomerView
-- 用于添加或编辑客户的视图
OrderListView
-- 列出所选customer
的所有订单,并在单击“添加订单”或“编辑订单”时打开OrderView
OrderView
-- 用于添加或编辑订单的视图
ViewModel
类旨在绑定到视图。这些类并非旨在复制已经完成的域模型(在业务层中)。
MainWindow
通过将应用程序的 DataContext
分配给 CustomerListViewModel
单例 来初始化它。
在 MainWindow
的 XAML 中,它通过使用 DataTemplates
指定要使用的视图(CustomerListView
和 OrderListView
)。
请注意,CustomerListView
绑定到 CustomerListViewModel
,而 OrderListView
绑定到 CustomerListViewModel
的 SelectedCustomer
属性,该属性是 CustomerViewModel
,它引用属于该客户的订单。以下是 CustomerListView
的 UI:
CustomerListView
绑定到 CustomerListViewModel
,我们指定了我们希望每个 UI 控件绑定的 ViewModel
的属性。
请注意,GridViewColumn
的 CellTemplate
定义了编辑和删除按钮。但更重要的是,ListView
的 SelectedItem
绑定到 ViewModel
的 SelectedCustomer
,当用户单击 customer
时,它会显示属于该 customer
的订单列表。这是因为 OrderListView
也绑定到 ViewModel
的 SelectedCustomer
。
以下是 OrderListView
的 XAML:
该按钮检查 DataContext
是否为 null
(当没有 customer
被选中时)。如果为 null
,则“添加订单”按钮被禁用。ListView
绑定到 CustomerViewModel
的 Orders
属性以显示订单列表。
单击“添加客户”或“编辑客户”按钮时会打开 CustomerView
窗口。以下是 CustomerView
的 UI,它可以是“添加客户”或“编辑客户”窗口:
窗口通过查看 CustomerViewModel
的 Mode
属性来确定它是“添加”还是“编辑”窗口。Mode
可以是“添加”或“编辑”。
CustomerView
通过查看 Mode
属性显示不同的文本。
单击窗口上的“添加”或“保存”按钮将调用 CustomerViewModel
的 Update()
方法,该方法根据 Mode
属性确定是添加还是编辑 customer
。
同样的概念也适用于 OrderView
。
执行命令
为了执行命令,我们编写了一个 CommandBase
基类,可以在 ViewModel
中使用。基类仅传递在执行命令时运行的委托。
然后,在 ViewModel
中,我们声明了视图可以绑定的命令。例如,在 CustomerViewModel
中,我们定义了删除 customer
的命令。
调用时将运行 Delete()
方法。然后,在 CustomerListView
中,我们指定了单击删除按钮时要执行的命令。
所有命令都以这种方式设置,以方便将命令绑定到 ViewModel
。
设置自动 UI 更新
接下来,我们需要确保当 ViewModel
的属性发生更改时,整个 UI 都会更新。这是通过包含一个 ViewModelBase
类来实现的,所有 ViewModel
都继承自该类。
例如,CustomerViewModel
继承自 ViewModelBase
类。
当 CustomerViewModel
的 FirstName
属性更改时(例如在编辑 customer
时),整个 UI 都应反映此更改。在 ViewModel
的属性中,我们只需调用 OnPropertyChanged
方法并传入属性名称,WPF 将处理其余部分。
UI 更新后的撤销
当用户取消更改时,您会遇到数据绑定问题。例如,用户可能在编辑客户窗口中更改客户的姓氏,这将更新整个 UI...
...然后决定通过按“取消”按钮来取消。使用默认的双向数据绑定,即使用户取消了更改,UI 也会保留更改后的值。
第一种想法可能是使用单向绑定,仅在用户单击“保存”按钮时才提交更改。这可以工作,但您会遇到更多问题:
- 您需要将多个值传递给保存命令,这会弄乱 XAML,您还需要将从命令传递的多值从
object
类型列表进行强制类型转换。 - 您将无法使用 WPF 提供的需要数据绑定的验证。
解决方法是允许 WPF 继续绑定,并在单击“取消”按钮时执行回滚。我们使用构造函数和 ViewModel
的 Update()
方法中的 MemberwiseClone()
存储原始值(尽管您也可以编写自己的深拷贝方法,如果您有引用类型字段)。
然后 ViewModel
中的 CancelCommand
将只调用 Undo()
并回滚到原始值,整个 UI 将被更新。
相同的方法也适用于编辑订单屏幕。
验证
以下是 OrderView
,其中“保存”按钮因验证消息而被禁用:
按钮的禁用是基于 OrderView.xaml
中 textbox
es 的 Validation.HasError
属性。
为了在文本框上执行验证,我们设置了 ValidatesOnDataErrors
、NotifyOnValidationError
和 UpdateSourceTrigger
属性。
UpdateSourceTrigger
的 PropertyChanged
值意味着在 textbox
中的每次击键都会执行验证。我们在 ValidationStyle.xaml 中定义了在发生错误时显示的 UI。
AdornedElementPlaceholder
显示正在验证的控件,而 ErrorContent
是验证消息字符串。
验证检查是在 ViewModel
中通过实现 IDataErrorInfo
接口来完成的。
在 IDataErrorInfo
接口的实现中,我们根据条件指定要显示的错误消息。
请注意,我们将 OrderViewModel
的 Quantity
属性设置为 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(您也可以使用其他框架)。
我们希望您觉得这个项目很有用。尽管还有很多事情可以做,但这应该能为您构建一个灵活的主从视图应用程序提供一个良好的起点。