MVVM疯狂:命令

63

我喜欢MVVM。虽然不是非常热爱,但还是喜欢它的大部分内容。但我继续阅读一些鼓励编写很多代码以便在XAML中编写任何代码的文章。

让我给你举个例子。

最近,我想将ViewModel中的一个命令连接到ListView的MouseDoubleClickEvent上。我不太确定如何实现这个功能。幸运的是,Google有关于这方面的答案。我找到了以下文章:

虽然这些解决方案在理解命令方面很有帮助,但也存在一些问题。其中一些解决方案会将"Internal"附加到依赖属性之后,导致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 纯粹主义者,请告诉我这种方式的问题在哪里?我仍然可以对我的命令进行单元测试。这似乎非常实用,但似乎违反了“天哪......你的代码在你的代码后台中!!!”的指导方针。请分享您的想法。

先行致谢。


1
我自己做过,我认为没有问题... - Jack Ukleja
7
哦天啊,代码背后!!!!! - Pierreten
1
我同意@Schneider的观点--这非常有道理。事实上,为了避免使用所谓的代码后台文件而需要编写的大量管道代码仍然让我感到困惑。 - Ralph Shillington
我是MVVM纯粹主义者,对此感到满意。唯一的区别在于可读性。然而,ListView_MouseDoubleClick事件可能会有些棘手,因为用户可能会在ListItem之外点击(例如滚动条、边框、填充等)。你最好使用ListViewItem.MouseDoubleClick。 - Liero
7个回答

37

我认为问题出在对纯度的要求上。设计模式,包括MVVM,是工具箱中的一种工具,而不是目的本身。如果为了一个经过深思熟虑的情况而打破模型的纯度更有意义(并且显然您已经考虑过这种情况),那么就打破模型吧。

如果这样做对您有效,并且您认为这不会带来过多的维护负担,那么我认为您所做的一切都没有问题。我认为,尽管与纯粹的MVVM实现相比有所违背,但您已经明确证明了这是一个合理的解决方案,以解决您的问题。

(我认为这个论点类似于支持多范式语言的论点。虽然可以采用纯面向对象的方法,但有时以更加函数化的方式处理事情更加合适。虽然可以采用纯函数式的方法,但有时权衡表明采用面向对象的技术是完全值得的。)


9
同意,无论是在MVVM模式还是其他任何模式下,过度纯净都会变得适得其反。我见过有人说你甚至不应该使用值转换器,因为你的视图模型应该完成所有工作 -- 这是一种紧密耦合和架构失败的绝佳配方。MVVM是一个(非常)有用的指导原则,教你如何在WPF中思考,以使你的代码简单和解耦。它永远不应成为一根棍子,用来将你的代码打成扭曲复杂的不自然混乱。 - itowlson
1
原问题反映了我自己尝试纯 MVVM 时的挫败感,我喜欢你的答案,但是我想指出,学习新模式,特别是像 MVVM 这样类似奥赛罗的模式(易学难精),最好的方法是尽一切所能迫使自己进入模式的纯洁性。只有在你学到了艰难的教训后,才能获得足够的经验,知道何时以及如何打破模式。 - Randolpho
1
@Randolpho:你提出了一个非常好的、有力的观点,我完全同意。在我的大多数个人项目中,我都追求模式理解的纯粹性,特别是小项目。我认为MVVM尤其棘手,因为关于视图和视图模型中应该包含什么存在很多灰色地带。我的个人观点是,任何不影响应用程序状态的东西都应该留在视图中(甚至是代码后台),而不是放在视图模型中,但这样写是否更像MVP呢?不知道。非常微妙的问题! - Greg D

13

我同意你的观点,许多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专家(即我的同事)阅读。


10

虽然我倾向于在使用MVVM模式时不编写代码后端,但只要该代码与UI纯粹相关,我认为这样做是可以的。

但是这里情况并非如此:你正在从代码后端调用视图模型命令,因此它不是纯粹的UI相关,并且视图与视图模型命令之间的关系在XAML中不是直接显而易见的。

我认为你可以很容易地在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设计师无法使用的链接。至少对我是这样。也许我做错了什么? - JP Richardson
5
“需要默认笑话:关于 WPF 设计师(Designer)难以使用的问题。” - Greg D
啊,是的,它确实会破坏设计师...我忘记了。无论如何,正如Greg D幽默地指出的那样,这个设计师几乎从来不工作;)。我从不使用它,我只写XAML... - Thomas Levesque
16
当我开始假装设计师不存在时,我的WPF生产力大幅提高。 - Robert Rossney
3
使用新的混合行为,附加行为就不再需要破坏设计师了。 - wekempf

5
我认为“代码不应该出现在后台”的目标确实是一个值得追求的目标,而不是你应该把它看作绝对教条。在视图中有适当的地方可以放置代码,这并不一定是代码可能比另一种方法更简单的坏示例。
你列出的其他方法的优点,包括附加属性或附加事件,是可重复使用的。当你直接挂钩一个事件,然后执行你所做的操作时,很容易在整个应用程序中复制该代码。通过创建一个单一的附加属性或事件来处理那个连接,你在管道中添加了一些额外的代码-但这是任何你想要双击处理的ListView都可以重复使用的代码。
话虽如此,我倾向于使用更“纯粹”的方法。将所有事件处理保持在视图之外可能不会影响测试场景(你特别提到了这一点),但它确实影响了总体的设计能力和可维护性。通过将代码引入你的代码后面,你限制了你的View始终使用已经连接了事件处理程序的ListView-这确实将你的View与代码联系起来,并限制了设计师重新设计的灵活性。

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);  
            }
        }
    }

计算机编程是不灵活的。我们程序员必须要有足够的灵活性来处理这种情况。


2

解耦是MVVM的主要特性之一。如果你想改变视图或绑定模型,那么对于你的应用程序来说有多容易呢?

举个例子,View1和View2共享同一个ViewModel。现在你会为两者都实现代码后台方法吗?

另外,假设在以后的某个阶段你需要为一个视图更改视图模型,那么你的命令将会失败,因为视图模型已经被更改了。

MyViewModel vm = this.DataContext as MyViewModel;

如果返回 null,代码就会崩溃。因此,在更改代码的同时还需要承担额外的负担。如果以这种方式进行操作,这些情况将会出现。

当然,在编程中有许多实现相同目标的方法,但哪一种最好将导致最佳方法。


"为两者实现代码后台方法。除了调用命令,OP在事件处理程序中没有实现任何逻辑。无论您是在XAML还是在代码后台执行此操作,都不会有任何区别,除了可读性。" - Liero
  1. "更改视图的视图模型": 视图始终与视图模型紧密耦合,无论您使用绑定还是从代码后台访问VM。实际上,代码后台更加类型安全。更改视图的VM(类型)不是常见做法。 "由于视图模型已更改,您的命令将失败...将返回null,因此代码崩溃" - 因此,您的XAML绑定将变得无效 - 修复它们甚至更加困难。
- Liero

1

命令是给傻瓜的。真正的男人会将整个用户界面与代码后台的事件相连接。


4
不,真正的 男人会用XAML编写他们的算法。 - Robert Fraser

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接