MVVM疯狂:命令

我喜欢MVVM。 我不喜欢它,但喜欢它。 大部分是有道理的。 但是,我一直在阅读鼓励您编写大量代码的文章,以便您可以编写XAML,而不必在代码隐藏中编写任何代码。

让我举一个例子。

最近我想在我的ViewModel中将一个命令连接到一个ListView的MouseDoubleClickEvent。 我不太确定如何做到这一点。 幸运的是,Google有一切的答案。 我发现了以下文章:

  • http://blog.functionalfun.net/2008/09/hooking-up-commands-to-events-in-wpf.html
  • http://joyfulwpf.blogspot.com/2009/05/mvvm-invoking-command-on-attached-event.html
  • http://sachabarber.net/?p=514
  • http://geekswithblogs.net/HouseOfBilz/archive/2009/08/27/adventures-in-mvvm-ndash-binding-commands-to-any-event.aspx
  • http://marlongrech.wordpress.com/2008/12/13/attachedcommandbehavior-v2-aka-acb/

虽然这些解决scheme对我对命令的理解很有帮助,但还是有问题的。 上述的一些解决scheme使得WPFdevise者无法使用,因为在依赖项属性之后添加了“内部” WPFdevise者找不到它,但是CLR可以。 一些解决scheme不允许多个命令到相同的控制。 一些解决scheme不允许参数。

经过几个小时的试验后,我决定这样做:

private void ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e) { ListView lv = sender as ListView; MyViewModel vm = this.DataContext as MyViewModel; vm.DoSomethingCommand.Execute(lv.SelectedItem); } 

那么,MVVM纯粹主义者,请告诉我这有什么问题? 我仍然可以unit testing我的命令。 这似乎很实用,但似乎违反了“ZOMG …你有代码在你的代码!!!!”的指导方针。 请分享你的想法。

提前致谢。

我认为这个错误在于纯度要求。 包含MVVM的devise模式是工具箱中的一个工具,而不是自己的目的。 如果打破一个考虑周到的案例的模型的纯度(显然看起来像你考虑过这个案例)是更有意义的,那么就打破这个模型。

如果这对你有用,你不相信这是一个不必要的维护负担,那么我会说,你所做的一切都没有错。 我认为,尽pipe纯粹的MVVM实现可能是什么,但您已经清楚地certificate了这是对您的问题的合理解决scheme。

(我认为这个论点类似于多范式语言的论点,虽然纯粹的面向对象方法是可以应用的,但是有时候以更实用的方式来做事情更合适,而纯粹的function方法是可以应用的,技术是值得的。)

我同意你的看法,许多MVVM-Command解决scheme太复杂了。 就我个人而言,我使用混合的方法,并使用ViewModel中的方法和属性在View中而不是在ViewModel中定义我的命令。

XAML:

 <Window.Resources> <RoutedCommand x:Key="LookupAddressCommand" /> </Window.Resources> <Window.CommandBindings> <CommandBinding Command="{StaticResource LookupAddressCommand}" x:Name="cmdLookupAddress" /> </Window.CommandBindings> 

代码(查看):

 Private Sub cmdLookupAddress_CanExecute(ByVal sender As System.Object, ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs) Handles cmdLookupAddress.CanExecute e.CanExecute = myViewModel.SomeProperty OrElse (myViewModel.SomeOtherProperty = 2) End Sub Private Sub cmdLookupAddress_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs) Handles cmdLookupAddress.Executed myViewModel.LookupAddress() End Sub 

这不是纯粹的MVVM,但它很简单,它的工作原理,它不需要特殊的MVVM命令类,它使您的代码更容易阅读非MVVM专家(=我的同事)。

尽pipe我不想在使用MVVM模式时编写代码隐藏,但我认为只要代码纯粹与UI相关,就可以这样做。

但是在这里并不是这样的:你从代码隐藏中调用了一个view-model命令,所以它不是纯粹的UI相关的,在XAML中,view和view-model命令之间的关系并不是直接的。

我想你可以使用附加的命令行为在XAML中轻松完成。 这样,您可以将MouseDoubleClick事件“绑定”到视图模型的命令上:

 <ListView ItemSource="{Binding Items}"> <local:CommandBehaviorCollection.Behaviors> <local:BehaviorBinding Event="MouseDoubleClick" Action="{Binding DoSomething}" /> </local:CommandBehaviorCollection.Behaviors> ... </ListView> 

您也可以使用ICollectionView界面轻松访问ListView的选定项目,而无需直接引用它:

 private ICommand _doSomething; public ICommand DoSomething { get { if (_doSomething == null) { _doSomething = new DelegateCommand( () => { ICollectionView view = CollectionViewSource.GetDefaultView(Items); object selected = view.CurrentItem; DoSomethingWithItem(selected); }); } return _doSomething; } } 

我相信,“在代码隐藏方面没有代码”的目标正是这样一个目标 – 而不是你应该采取的绝对教条。 在视图中有适当的代码位置 – 这不一定是代码在哪里或如何比其他方法更简单的坏例子。

您列出的其他方法(包括附加属性或附加事件)的优点是它们可以重复使用。 当你直接连接一个事件,然后做你所做的事情,很容易在你的应用程序中复制这些代码。 通过创build一个单独的属性或事件来处理连线,可以在pipe道中添加一些额外的代码 – 但是它是可以在任何需要双击处理的ListView中重复使用的代码。

这就是说,我倾向于使用更“纯粹”的方法。 保持View的所有事件处理不会影响testing场景(您特别强调的),但它确实会影响整体devise性和可维护性。 通过在代码中引入代码,您将视图限制为始终使用带有事件处理函数的ListView – 这将视图绑定到代码中,并限制devise人员重新devise的灵活性。

@JP在原始问题中描述的和@Heinzi提到的答案是处理困难命令的实用方法。 在后台代码中使用一小部分事件处理代码在调用该命令之前需要做一些UI工作时尤其方便。

考虑一下OpenFileDialog的经典案例。 在button上使用单击事件,显示对话框,然后将结果发送到ViewModel上的命令要比采用MVVM工具包使用的任何复杂的消息传递例程要容易得多。

在你的XAML中:

 <Button DockPanel.Dock="Left" Click="AttachFilesClicked">Attach files</Button> 

在你的代码背后:

  private void AttachFilesClicked(object sender, System.Windows.RoutedEventArgs e) { // Configure open file dialog box Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog(); dlg.FileName = "Document"; // Default file name dlg.DefaultExt = ".txt"; // Default file extension dlg.Filter = "Text documents (.txt)|*.txt"; // Filter files by extension // Show open file dialog box bool? result = dlg.ShowDialog(); // Process open file dialog box results if (result == true) { string filename = dlg.FileName; // Invoke the command. MyViewModel myViewModel = (MyViewModel)DataContext; if (myViewModel .AttachFilesCommand.CanExecute(filename)) { noteViewModel.AttachFilesCommand.Execute(filename); } } } 

计算机编程不灵活。 我们程序员必须灵活处理这个问题。

解耦是MVVM的一个主要特性。 如果假设你想改变说视图或绑定模型。 你的应用程序有多简单?

以View1和View2共享同一个ViewModel为例。 现在你将实现两个方法的代码。

此外,假设如果您需要在后期更改视图的视图模型,您的命令将失败,因为视图模型已更改,语句

 MyViewModel vm = this.DataContext as MyViewModel; 

将返回null,从而代码崩溃。 所以,还有一个额外的负担来改变背后的代码。 如果你这样做,会出现这种情况。

当然,在编程中有很多方法可以达到同样的效果,但是哪一种最好会导致最好的方法。

指挥是为了突破。 真正的男人把他们的整个用户连接到代码隐藏的事件。