不幸的是,没有一个伟大的MVVM示例应用程序可以执行所有操作,并且有许多不同的执行方法。首先,您可能想熟悉其中的一个应用程序框架(Prism是一个不错的选择),因为它们为您提供了方便的工具,例如依赖项注入,命令,事件聚合等,可以轻松地尝试适合您的不同模式。
棱镜发布:http :
//www.codeplex.com/CompositeWPF
它包括一个相当不错的示例应用程序(股票交易员),以及许多较小的示例以及操作方法。至少,它很好地展示了人们用来使MVVM实际工作的几种常见子模式。我相信,它们都有CRUD和对话框的示例。
棱镜不一定适用于每个项目,但熟悉它是一件好事。
CRUD:
这部分非常容易,WPF双向绑定使编辑大多数数据非常容易。真正的诀窍是提供一个易于设置UI的模型。至少要确保您的ViewModel(或业务对象)实现INotifyPropertyChanged
为支持绑定,并且可以将属性直接绑定到UI控件,但是您可能还希望实现IDataErrorInfo
以进行验证。通常,如果您使用某种ORM解决方案,则设置CRUD非常容易。
本文演示了简单的操作:http :
//dotnetslackers.com/articles/wpf/WPFDataBindingWithLINQ.aspx
它基于LinqToSql构建,但这与示例无关-重要的是您的业务对象要实现INotifyPropertyChanged
(由LinqToSql生成的类可以实现)。MVVM不是该示例的重点,但是在这种情况下,我认为这并不重要。
本文演示了数据验证
http://blogs.msdn.com/wpfsdk/archive/2007/10/02/data-validation-in-3-5.aspx
同样,大多数ORM解决方案都会生成已经实现的类,IDataErrorInfo
并且通常会提供一种使添加自定义验证规则变得容易的机制。
大多数时候,您可以将某些ORM创建的对象(模型)包装到一个包含有该对象和用于保存/删除的命令的ViewModel中,并且可以将UI直接绑定到该模型的属性。
视图看起来像这样(ViewModel具有一个Item
保存模型的属性,就像在ORM中创建的类一样):
<StackPanel>
<StackPanel DataContext=Item>
<TextBox Text="{Binding FirstName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
<TextBox Text="{Binding LastName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
</StackPanel>
<Button Command="{Binding SaveCommand}" />
<Button Command="{Binding CancelCommand}" />
</StackPanel>
对话框:
对话框和MVVM有点棘手。我更喜欢在对话框中使用Mediator方法,您可以在以下StackOverflow问题中阅读更多有关它的信息:
WPF MVVM对话框示例
我通常的方法不是很经典的MVVM,可以总结如下:
对话框ViewModel的基类,它公开用于提交和取消操作的命令,一个事件以使视图知道对话框已准备就绪,可以关闭,以及所有对话框中需要的其他东西。
对话框的通用视图-可以是窗口,也可以是自定义的“模式”覆盖类型控件。从本质上讲,这是一个内容演示者,我们将viewmodel转储到其中,并处理关闭窗口的连线-例如,在数据上下文更改时,您可以检查新ViewModel是否继承自您的基类,如果是,订阅相关的关闭事件(处理程序将分配对话框结果)。如果提供其他通用关闭功能(例如X按钮),则应确保在ViewModel上也运行相关的关闭命令。
在某些地方需要为ViewModels提供数据模板,它们可能非常简单,特别是因为您可能在单独的控件中封装了每个对话框的视图。然后,ViewModel的默认数据模板将如下所示:
<DataTemplate DataType="{x:Type vmodels:AddressEditViewModel}">
<views:AddressEditView DataContext="{Binding}" />
</DataTemplate>
对话框视图需要访问这些视图,因为否则它将不知道如何显示ViewModel,除了共享对话框UI之外,其内容基本上是这样的:
<ContentControl Content="{Binding}" />
隐式数据模板会将视图映射到模型,但是谁启动了它?
这不是mvvm的一部分。一种方法是使用全局事件。我认为最好的方法是使用通过依赖项注入提供的事件聚合器类型设置-这样,事件对于容器是全局的,而不是整个应用程序。Prism使用unity框架进行容器语义和依赖注入,总体而言,我相当喜欢Unity。
通常,对于根窗口订阅此事件是有意义的-它可以打开对话框并将其数据上下文设置为随引发事件传递的ViewModel。
以这种方式进行设置使ViewModels可以在不了解UI的情况下要求应用程序打开对话框并在其中响应用户操作,因此在大多数情况下MVVM保持完整。
但是,有时UI必须引发对话框,这会使事情变得有些棘手。例如,考虑对话框的位置是否取决于打开对话框的按钮的位置。在这种情况下,当您请求打开对话框时,您需要具有一些特定于UI的信息。我通常创建一个单独的类,其中包含ViewModel和一些相关的UI信息。不幸的是,那里似乎不可避免地存在一些耦合。
按钮处理程序的伪代码,它引发一个需要元素位置数据的对话框:
ButtonClickHandler(sender, args){
var vm = DataContext as ISomeDialogProvider; // check for null
var ui_vm = new ViewModelContainer();
// assign margin, width, or anything else that your custom dialog might require
...
ui_vm.ViewModel = vm.SomeDialogViewModel; // or .GetSomeDialogViewModel()
// raise the dialog show event
}
对话框视图将绑定到位置数据,并将包含的ViewModel传递给inner ContentControl
。ViewModel本身仍然对UI一无所知。
通常,我不使用方法的DialogResult
return属性,也不要ShowDialog()
期望线程在对话框关闭之前阻塞。非标准的模态对话框并不总是那样工作,在复合环境中,您通常通常不希望事件处理程序那样阻塞。我更喜欢让ViewModels处理-ViewModel的创建者可以订阅其相关事件,设置commit / cancel方法等,因此无需依赖此UI机制。
因此,代替此流程:
// in code behind
var result = somedialog.ShowDialog();
if (result == ...
我用:
// in view model
var vm = new SomeDialogViewModel(); // child view model
vm.CommitAction = delegate { this.DoSomething(vm); } // what happens on commit
vm.CancelAction = delegate { this.DoNothing(vm); } // what happens on cancel/close (optional)
// raise dialog request event on the container
我更喜欢这种方式,因为我的大多数对话框都是非阻塞的伪模式控件,并且这种方式似乎比解决它更简单。易于进行单元测试。