MVVM模板的好例子

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (11)

正在使用MicrosoftMVVM模板,发现缺乏详细的示例令人沮丧。包含的ContactBook示例显示的命令处理非常少,是否有任何适当的MVVM示例至少显示了基本的CRUD操作和对话框/内容切换?

提问于
用户回答回答于

没有一个伟大的MVVM示例应用程序可以完成所有的工作,并且有很多不同的方法来做事情。

首先,需要熟悉其中一个应用程序框架(Prism是一个不错的选择),因为它们提供了方便的工具,如依赖注入、命令、事件聚合等,以便轻松地尝试适合不同模式。

它包括一个相当不错的例子应用程序(股票交易员),以及许多较小的例子和如何。至少,它很好地演示了人们用来使MVVM实际工作的几个常见子模式。我相信他们有CRUD和对话框的例子。

棱镜并不一定适用于每一个项目,但熟悉它是一件好事。

CRUD:这部分非常容易,WPF双向绑定使得编辑大部分数据变得非常容易。真正的诀窍是提供一个模型,使建立UI变得容易。至少要确保ViewModel(或业务对象)实现INotifyPropertyChanged若要支持绑定,可以将属性直接绑定到UI控件,但也希望实现IDataErrorInfo为了验证。通常,如果您使用某种ORM解决方案,那么设置CRUD非常简单。

它构建在LinqToSql上,但这与示例无关--重要的是您的业务对象实现。INotifyPropertyChanged(LinqToSqldo生成的类)。

同样,大多数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问题中更多地了解它:

我通常的方法(不是很经典的MVVM)可以概括如下:

对话框视图模型的一个基类,它公开提交和取消操作的命令,一个事件,让视图知道对话框已经准备好关闭,以及您在所有对话框中需要的其他任何东西。

对话框的通用视图--这可以是一个窗口,也可以是一个自定义的“模式”覆盖类型控件。它的核心是一个内容演示器,我们将视图模型转储到其中,它处理关闭窗口的连接--例如,在数据上下文更改时,可以检查新的ViewModel是否从基类继承,如果是,则订阅相关的关闭事件(处理程序将分配对话框结果)。如果提供了替代的通用关闭功能(例如,X按钮),那么应该确保在ViewModel上运行相关的Close命令。

在需要为ViewModel提供数据模板的地方,它们可能非常简单,特别是因为可能有一个将每个对话框封装在单独控件中的视图。然后,ViewModel的默认数据模板如下所示:

<DataTemplate DataType="{x:Type vmodels:AddressEditViewModel}>
   <views:AddressEditView DataContext={Binding} />
</DataTemplate>

对话框视图需要访问这些视图,因为否则它将不知道如何显示ViewModel,除了共享对话框UI之外,它的内容基本上如下:

<ContentControl Content={Binding} />

隐式数据模板将视图映射到模型,但谁启动它呢?

这是不那么MVVM的部分。一种方法是使用全局事件。我认为更好的做法是使用通过依赖注入提供的事件聚合器类型设置--这样事件对容器是全局的,而不是整个应用程序。Prism使用统一框架进行容器语义和依赖项注入,总的来说,我非常喜欢Unitity。

通常,根窗口订阅此事件是有意义的-它可以打开对话框并将其数据上下文设置为与引发的事件一起传入的ViewModel。

通过这种方式设置,ViewModel可以让应用程序打开一个对话框,并在不了解UI的情况下响应用户操作,因此MVVMNness在很大程度上仍然是完整的。

然而,有时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传递给内部ContentControl

一般来说,我不使用DialogResult属性的返回属性。ShowDialog()方法或期望线程阻塞,直到对话框关闭。非标准的模态对话框并不总是那样工作,而且在复合环境中,不希望事件处理程序像这样阻塞。我更愿意让ViewModel来处理这个问题--ViewModel的创建者可以订阅它的相关事件,设置提交/取消方法等等,所以不需要依赖这种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

用户回答回答于

大多数是很好的MVVM示例,但当您进入复杂的问题时就不是了。每个人都有自己的方式。LaurentBugnion也有一个很好的方式来在视图模型之间进行交流。

扫码关注云+社区