【问题标题】:Does calling View Model methods in Code Behind events break the MVVM?在代码隐藏事件中调用视图模型方法会破坏 MVVM 吗?
【发布时间】:2016-10-21 07:27:07
【问题描述】:

我想知道这是否会破坏 MVVM 模式,如果是,为什么会如此糟糕?

WPF:

<Button Click="Button_Click" />

代码隐藏:

private void Button_Click(object sender, RoutedEventArgs e)
{
    ViewModel.CallMethod();
}

查看模型:

public void CallMethod()
{
    // Some code
}

恕我直言,它使背后的代码非常简单,视图模型仍然不知道视图和背后的代码,并且视图的更改不会影响业务逻辑。

在我看来,它比CommandsCallMethodAction 更简单明了。

我不想要“这不是应该怎么做”的那种回答。我需要一个适当且合乎逻辑的理由来说明为什么这样做会导致维护或理解问题。

【问题讨论】:

  • 您通常会将 Button 的 Command 属性绑定到执行该方法的视图模型中的 ICommand 属性。但是,按照您在此处显示的方式进行操作不会破坏任何内容。
  • 视图与视图模型的交互尽可能像鸭子类型是很好的。在某些情况下,您希望能够将给定视图与两个不相关的视图模型一起使用,这些视图模型恰好具有相同名称和类型的属性,它们都可以与它们绑定的控件一起使用。但很多时候,视图是高度专业化的,这不会成为考虑因素。在这些情况下,强类型的ViewModel 属性是常见的做法。每个视图模型的多个视图比每个视图多个不相关的视图模型更常见。

标签: c# wpf mvvm


【解决方案1】:

不,这非常好

处理用户输入并与 ViewModel 交互是 View 的工作。一个按钮单击事件处理程序,它调用 ViewModel 的一个方法作为响应,完全属于这个角色。

您发布的内容干净、可读、高效、可维护,并且完全符合 MVVM 设计模式的精神。

现在,从更一般的意义上说,您真正想问的是:“为什么选择 ICommands,而不是 MVVM 的事件处理程序?”好吧,你肯定不会be | the | first.

【讨论】:

  • 感谢您的回答和提供进一步见解的链接。
  • 类之间不应紧密耦合。如果您在谈论 XAML,那么是的,这也需要知道要绑定什么属性,但绝对背后的代码不应该知道 VM。
  • @SledgeHammer 我完全不同意。视图可以在代码隐藏或 XAML 中实现,没关系。 MVVM 的症结在于“Views 知道 ViewModels,但 ViewModels 不知道 VIews”。耦合是单向的,这意味着 ViewModel 是完全可移植和可重用的,而 View 则不是。
【解决方案2】:

不,它不会破坏 MVVM,只要您不引入更适合属于视图模型的逻辑。

它有可能降低清晰度,IMO,因为它将视图分解为紧密耦合的 XAML 和 c# 文件,您无法在一个地方看到所有内容。我发现零代码更容易,因为这意味着在视图上工作时上下文切换更少。

在您的 UI 设计师不是 C# 程序员的环境中工作也会变得更具挑战性,因为这样会由两个不同的人维护紧密耦合的文件。

编辑

这是我的意思的一个例子。这是我为了乐趣和体验而在 WPF 中实现扫雷的周末项目。我最大的 WPF 挑战之一是鼠标输入。对于以前没有在游戏上浪费时间的任何人,鼠标左键单击显示一个单元格,鼠标右键切换单元格上的一个标志,鼠标中键将(有条件地)显示相邻的单元格。

我首先考虑使用 System.Windows.Interactivity 的 EventTriggerInvokeCommandAction 将事件映射到命令。这种方法适用于鼠标右键(不是真正的单击事件,而是 MouseRightButtonUp),但它根本不适用于没有特定操作的鼠标中键,只有通用的 MouseDown/MouseUp。我简要地考虑了 prism 在 InvokeCommandAction 上的变体,它可以将 MouseButtonEventArgs 传递给它的处理程序,但这非常破坏了 MVVM 概念,我很快就放弃了它。

我不喜欢将事件处理程序直接放在代码隐藏中的想法,因为这将动作(鼠标单击)和响应(显示单元格等)紧密结合在一起。这也不是一个非常可重用的解决方案 - 每次我想要处理中间点击时,我都会复制、粘贴和编辑。

我的决定是这样的:

<i:Interaction.Triggers>
    <mu:MouseTrigger MouseButton="Middle" MouseAction="Click">
        <i:InvokeCommandAction Command="{Binding Path=RevealAdjacentCells}" />
    </mu:MouseTrigger>
</i:Interaction.Triggers>

在这种情况下,后面的代码中没有代码。我将它移到我创建的 MouseTrigger 类中,该类继承自 System.Windows.Interactivity.TriggerBase,虽然是视图层代码,但它不是任何特定视图的一部分,而是任何视图都可以使用的类。这个处理程序代码尽可能不知道它附加到什么样的元素 - 从 UIElement 派生的任何东西都可以工作。

通过利用这种方法,与在代码隐藏的事件处理程序中执行此操作相比,我获得了两个关键:

  1. 事件和动作之间存在松散耦合。如果我有一个在 UI 上工作的 UXD,他们可以通过编辑一行 XAML 来更改命令关联的鼠标按钮。例如,交换鼠标右键和中键很简单,不需要更改 .cs。
  2. 它可以在任何 UIElement 上重复使用,而不是绑定到任何特定的元素。我可以在以后需要解决此类问题的任何时候将其取出。

这里的主要缺点是最初的设置工作量更大。我的 MouseTrigger 类比事件处理程序本身更复杂(主要是围绕正确处理依赖属性及其更改)。对于看似简单的事情,XAML 通常也可能相当冗长。

【讨论】:

  • 关于三权分立的好观点。但是,即使设计者不必关心背后的代码,他仍然必须对与 UI 设计无关的 Commands 或 CallMethodAction 有一个很好的理解,所以我觉得创建事件并不困难并告诉他它们的名字和含义。
  • 确实需要对命令有一定程度的理解。但是,该命令的耦合不那么紧密。如果 UI 设计人员决定在列表更改时删除按钮并运行操作怎么办?至少,Button_Click 应该被重命名。或者,如果他们希望在菜单、工具栏或鼠标操作上提供相同的操作?在某些情况下,您可以使用通用事件处理程序,直到您需要使用事件参数。当您需要触发一系列事件时,它会变得更加复杂。如果你愿意,当我下班回家时,我可以添加一个最近的简短示例。
  • 更改事件名称与更改命令名称相同,对吧?如果您需要使用事件参数,那么命令不是也开始紧密耦合了吗?如果命令依赖于鼠标位置,我可以轻松地将命令从 MouseMove 移动到 Click 吗?在我看来,如果必须进行更改,工作量将非常相似。另外,在仅使用事件的情况下,您可以将事件争论(这是视图的责任)转换为不可知的方法参数。
  • 我绝对同意您必须在视图层中处理任何与事件参数相关的部分 - 但不一定在特定视图的代码隐藏中。你是对的,对命令名称的任何更改都需要在 XAML 中进行更改,但根据我的经验,CalculateShipping 命令的存在不如 UI 调用命令的机制那么流畅(它是按下按钮,是否填写了有效的邮政编码等)。我更喜欢围绕最有可能改变的部分松散耦合。编辑了我的答案以显示示例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多