MVVM模板的好例子


141

我目前正在使用Microsoft MVVM模板,发现缺少详细示例令人沮丧。包含的ContactBook示例只显示了很少的命令处理,而我发现的唯一另一个示例是从MSDN Magazine文章中获得的,该示例的概念相似,但使用的方法略有不同,但仍然没有任何复杂性。是否有任何不错的MVVM示例,至少显示了基本的CRUD操作和对话框/内容切换?


每个人的建议都非常有用,我将开始汇编一份很好的资源清单

框架/模板

有用的文章

截屏

附加图书馆


我很高兴这些资源有所帮助。我目前正在第二个生产MVVM应用程序上,并将继续添加内容,这些内容将对那些初次接触的人有所帮助。
jwarzech 2010年

Answers:


59

不幸的是,没有一个伟大的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一无所知。

通常,我不使用方法的DialogResultreturn属性,也不要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

我更喜欢这种方式,因为我的大多数对话框都是非阻塞的伪模式控件,并且这种方式似乎比解决它更简单。易于进行单元测试。


感谢您的详细回答!最近,我发现最大的问题是何时需要MainViewModel与其他视图模型进行通信以处理应用程序的流程。但是,似乎MVVM + Mediator似乎是流行的方法。
jwarzech

2
中介器绝对有帮助,当目标为低耦合时,事件聚合器模式(Prism具有良好的实现)也非常有用。另外,您的主视图模型通常具有自己的子视图模型,与它们进行通信时应该没有问题。当您的子视图模型需要与应用程序中的其他模块(不一定包括UI)进行交互时,您需要使用调解器或事件聚合器(包括UI)(我的对话框示例就是针对这种情况)。
Egor

1
使用对话框和窗口的准则确实很有帮助。但是,我遇到了一些问题:1.如何从视图中设置窗口标题?2.如何处理设置所有者窗口?
djskinner,2010年

@丹尼尔·斯金纳(Daniel Skinner):我假设您在这里谈论对话,如果我错了,请纠正我。对话框标题只是另一个属性,您可以将其绑定到所需的任何内容。如果您按照我的方法使用基本对话框viewmodel类(假设它具有title属性),则可以在所有通用对话框窗口中使用UI到UI绑定将标题设置为{Binding Path = DataContext.Title,ElementName = NameOfContentPresenter}。“所有者”窗口有点麻烦-这意味着实际弹出对话框的中介者需要了解根应用程序视图。
Egor

实际上,我会收回这一点-无论您在某个时候如何构建此结构,无论谁实际弹出对话框,都需要引用根应用程序窗口/视图。请注意,我在上面说的是:“通常,根窗口订阅该事件是有意义的-它可以打开对话框并将其数据上下文设置为通过引发事件传递的视图模型。” 这是设置所有者的位置。
Egor

6

Jason Dolinger 对MVVM 作了很好的截屏。就像Egor提到的那样,没有一个很好的例子。他们都结束了。大多数都是很好的MVVM示例,但是当您遇到复杂问题时则不是。每个人都有自己的方式。Laurent Bugnion也是在视图模型之间进行通信的好方法。 http://blog.galasoft.ch/archive/2009/09/27/mvvm-light-toolkit-messenger-v2-beta.aspx Cinch也是一个很好的例子。保罗·斯托维尔(Paul Stovel)的一篇不错的文章也对他的麦哲伦框架做了很多解释。


3

你看过卡利本了吗?ContactManager示例中包含很多好东西。通用的WPF示例还提供了很好的命令概述。文档非常好,论坛也很活跃。推荐的!





2

我也分享了你的无奈。我正在编写一个应用程序,我有以下3个要求:

  • 可扩展的
  • 具有MVVM的WPF
  • GPL兼容示例

我发现的只是点点滴滴,所以我才尽力开始写它。经过一番研究之后,我意识到可能还有其他人(像您一样)可以使用参考应用程序,因此我将通用内容重构为WPF / MVVM应用程序框架,并在LGPL下发布了它。我将其命名为SoapBox Core。如果转到下载页面,您会看到它带有一个小型演示应用程序,该演示应用程序的源代码也可供下载。希望对您有所帮助。另外,如果您需要更多信息,请通过scott {at} soapboxautomation.com向我发送电子邮件。

编辑:还发布了CodeProject文章,说明其工作方式。



1

甚至我都感到沮丧,直到把事情交到我手中。我启动了IncEditor。

IncEditor(http://inceditor.codeplex.com)是一个编辑器,旨在向开发人员介绍WPF,MVVM和MEF。我启动了它,并设法获得了一些功能,例如“主题”支持。我不是WPF,MVVM或MEF方面的专家,所以我不能在其中添加很多功能。我真诚地要求你们做得更好,以便像我这样的小伙子能够更好地理解它。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.