MVVM 疯狂:命令
Posted
技术标签:
【中文标题】MVVM 疯狂:命令【英文标题】:MVVM Madness: Commands 【发布时间】:2011-01-06 11:04:57 【问题描述】:我喜欢 MVVM。我不喜欢它,但喜欢它。大部分都是有道理的。但是,我一直在阅读鼓励您编写大量代码以便编写 XAML 而不必在代码隐藏中编写任何代码的文章。
让我举个例子。
最近我想将 ViewModel 中的命令连接到 ListView MouseDoubleClickEvent。我不太确定该怎么做。幸运的是,谷歌对一切都有答案。我找到了以下文章:
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/虽然这些解决方案有助于我理解命令,但也存在问题。前面提到的一些解决方案使 WPF 设计器无法使用,因为在依赖属性后附加“内部”的常见技巧; WPF 设计器找不到它,但 CLR 可以。一些解决方案不允许对同一个控件执行多个命令。一些解决方案不允许使用参数。
经过几个小时的实验,我决定这样做:
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 纯粹主义者,请告诉我这有什么问题?我仍然可以对我的命令进行单元测试。这似乎很实用,但似乎违反了“ZOMG ...您的代码隐藏中有代码!!!!”的准则请分享您的想法。
提前致谢。
【问题讨论】:
我自己做的,我觉得没问题... ZOMFG 代码隐藏!!!!!! 我同意@Schneider 的观点——这很有意义。事实上,避免使用所谓的代码隐藏文件所需的大量管道代码仍然让我头晕目眩。 【参考方案1】:解耦是 MVVM 的一大特色。如果假设您想将视图或绑定模型更改为它。您的应用程序有多容易?
以 View1 和 View2 共享同一个 ViewModel 为例。现在您将为两者实现代码隐藏方法。
另外,假设如果您需要在后期更改视图的视图模型,您的命令将失败,因为视图模型和语句发生了变化
MyViewModel vm = this.DataContext as MyViewModel;
将返回 null 并因此导致代码崩溃。因此,更改后面的代码也会带来额外的负担。如果你这样做,就会出现这种情况。
当然,在编程中有很多方法可以实现相同的目标,但哪种方法最好会导致最好的方法。
【讨论】:
【参考方案2】:@JP 在原始问题中的描述和@Heinzi 在答案中提到的是一种处理困难命令的务实方法。当您需要在调用命令之前做一些 UI 工作时,在后面的代码中使用一点点事件处理代码会特别方便。
考虑 OpenFileDialog 的经典案例。使用按钮上的单击事件、显示对话框然后将结果发送到 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);
计算机编程是不灵活的。我们程序员必须灵活处理这个问题。
【讨论】:
【参考方案3】:指挥是为笨蛋准备的。真正的男人将他们的整个用户界面连接到代码隐藏中的事件。
【讨论】:
不,REAL 人用 XAML 编写算法。【参考方案4】:我认为问题在于纯度要求。包括 MVVM 在内的设计模式是工具箱中的一个工具,它们本身并不是目的。如果对一个经过深思熟虑的案例(显然您已经考虑过这个案例)打破模型的纯粹性更有意义,那么就打破模型。
如果这对您有用,并且您不认为这是过度的维护负担,那么我会说您所做的一切都没有问题。我认为您显然已经承担了举证责任,证明这是对您的问题的合理解决方案,尽管可能是纯 MVVM 实现。
(我认为这个论点类似于多范式语言的论点。虽然可以应用纯 OO 方法,但有时以更实用的方式做事更合适。虽然可以应用纯函数方法,但有时需要权衡取舍表明 OO 技术非常值得。)
【讨论】:
同意,无论是使用 MVVM 还是任何其他模式,过度的纯度都会适得其反。我看到有人说你甚至不应该使用值转换器,因为你的视图模型应该做所有的工作——这是紧密耦合和架构失败的必经之路。 MVVM 是在 WPF 中如何思考的一个(非常)有用的指导原则,以使您的代码变得简单和解耦。它永远不应该成为一根棍子,用来将您的代码打造成扭曲复杂的不自然混乱。 最初的问题反映了我自己尝试做纯 MVVM 的挫败感,我喜欢你的回答,但是我想指出的是,在学习一种新模式时,尤其是像 MVVM 这样的黑白棋(容易学习,难以掌握),实现这种学习的最好方法是尽你所能强迫自己进入模式纯粹性。只有在你吸取了惨痛的教训之后,你才能获得必要的经验,知道何时以及如何打破这种模式。 @Randolpho:你提出了一个非常好的和有说服力的观点,我完全同意。在我的大多数个人项目中,我都力求对模式的理解保持纯正,当然还有小项目。我认为 MVVM 尤其棘手,因为视图与视图模型之间存在太多灰色区域。我个人的看法是,任何不影响应用程序状态的东西都不会出现在虚拟机和视图中(甚至可能是代码隐藏!),但是我是否在编写更像 MVP 的东西?不知道。非常微妙的东西!【参考方案5】:虽然我不喜欢在使用 MVVM 模式时编写代码隐藏,但我认为只要该代码纯粹与 UI 相关,就可以这样做。
但这不是这里的情况:您从代码隐藏中调用 view-model 命令,因此它不是纯粹与 UI 相关的,并且 view 和 view-model 命令之间的关系并不直接明显在 XAML 中。
我认为您可以使用 attached command behavior 在 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;
【讨论】:
我也打算放这个链接。但这是导致 WPF 设计器无法使用的原因之一。至少它对我有用。也许我做错了什么? 啊,是的,它确实破坏了设计师……我忘记了。无论如何,正如 Greg D 幽默指出的那样,这个设计师几乎从不工作;)。我从不使用它,我只写 XAML... 当我开始假装设计师不存在时,我的 WPF 工作效率大大提高。 使用新的混合行为,此类事情的附加行为不再需要破坏设计师。【参考方案6】:我相信“代码隐藏中没有代码”的目标正是要达到的目标,而不是您应该将其视为绝对教条的目标。视图中有适当的代码位置 - 这不一定是代码在何处或如何比替代方法更简单的一个坏例子。
您列出的其他方法(包括附加属性或附加事件)的优点是它们是可重用的。当您直接连接一个事件,然后执行您所做的操作时,很容易在整个应用程序中复制该代码。通过创建单个附加属性或事件来处理该连接,您可以在管道中添加一些额外的代码 - 但它是可重复用于您想要双击处理的任何 ListView 的代码。
话虽如此,我更喜欢使用更“纯粹”的方法。将所有事件处理保留在视图之外可能不会影响测试场景(您专门解决),但它确实会影响整体可设计性和可维护性。通过在后面的代码中引入代码,您将 View 限制为始终使用带有事件处理程序的 ListView - 这确实将您的 View 绑定到代码中,并限制了设计人员重新设计的灵活性。
【讨论】:
【参考方案7】:我同意你的观点,许多 MVVM-Command 解决方案过于复杂。就个人而言,我使用混合方法并在视图中而不是在 ViewModel 中定义我的命令,使用来自 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 专家(= 我的同事)更容易阅读您的代码。
【讨论】:
以上是关于MVVM 疯狂:命令的主要内容,如果未能解决你的问题,请参考以下文章