为什么在WPF MVVM模式中要避免使用CodeBehind?

64
在文章使用模型-视图-视图模型设计模式的WPF应用程序中,作者Josh Smith说:
(1) 在一个良好设计的MVVM架构中,大多数View的codebehind应该是空的,或者最多只包含操作该View内的控件和资源的代码。(2) 有时也需要在View的codebehind中编写与ViewModel对象交互的代码,例如挂接事件或调用从ViewModel本身很难调用的方法。
我的问题是,在(1)处,为什么空的codebehind被视为良好设计的MVVM。(听起来空的codebehind总是好的。)
编辑:我的问题是,为什么要避免像AttachedCommandBehavior或InvokeCommandAction这样的方法来避免codebehind编码。
让我解释得更详细一些。
就(1)而言,我认为从AttachedCommandBehavior的角度来看,由于边框没有实现ICommandSource用于MouseRightButtonDown,因此您不能普遍地绑定事件和ICommand,但可以使用AttachedCommandBehavior
<!-- I modified some code from the AttachedCommandBehavior to show more simply -->
<Border>
    <local:CommandBehaviorCollection.Behaviors>
           <local:BehaviorBinding Event="MouseRightButtonDown" 
                  Command="{Binding SomeCommand}" 
                  CommandParameter="A Command on MouseRightButtonDown"/>
    </local:CommandBehaviorCollection.Behaviors>
</Border>

或者

我们可以使用System.Windows.Interactivity.InvokeCommandAction来实现这一点。

<Border xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" >
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseRightButtonDown">
            <i:InvokeCommandAction Command="{Binding SomeCommand}" 
               CommandParameter="A Command on MouseRightButtonDown"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Border>

但是,

我们使用以下XAML及其代码后台,具有Border_MouseRightButtonDown方法,该方法链接到上面(2) Josh Smith所说的内容。

<Border MouseRightButtonDown ="Border_MouseRightButtonDown"/>

我认为使用上述的codebehind并不差,因为它们之间的区别仅在于绑定命令或添加事件处理程序的位置。

您对此有何看法?


7
我认为,如果Border_MouseRightButtonDown事件不会触发ViewModel中的任何操作,也不会修改其状态,那么完全可以这样做。在ViewModel中放置大量UI代码最终会导致将代码转移到ViewModel中。 - v00d00
5个回答

91
拥有一个仅包含在其构造函数中调用InitializeComponent()的代码后台文件意味着您已经实现了纯净性——您的代码后台中绝对没有逻辑。您没有将任何应该属于视图模型或模型的代码污染到视图中。这意味着几件事:
- 视图模型(和模型)更容易在隔离中进行测试 - 您已经实现了良好的松耦合,从维护和可扩展性的角度来看,具有极好的优势
当您必须更改UI时,即从使用ListView切换到DataGrid,或者从使用标准Microsoft控件更改为使用其他供应商的控件时,优点真正变得明显。
尽管如此,有时不可能避免在代码后台文件中编写一些代码。您应确保您拥有的代码纯粹与UI相关。例如,如果您有ComboA和ComboB,并且ComboB是响应于ComboA中的选择而设置的,则从视图设置ComboB的SelectedIndex是可以的,但设置ComboB的Items或SelectedItem不行——这些属性都与数据相关,应通过绑定到视图模型来指定。SelectedIndex属性直接与可视化相关,与实际数据有些独立(并且对视图模型无关)。
如果您需要从视图中的代码后台访问viewmodel,则应尝试通过接口进行访问。这意味着将viewmodel作为接口注入或提供给视图。(请注意,绑定子系统不知道也不关心接口,它将以正常方式继续绑定。这样可以实现更好的代码,减少紧密耦合)。按照我的编码方式,viewmodel不知道存在一个视图,而视图只将viewmodel视为接口。
但要记住MVVM是一种模式,而模式只是在特定情况下实现特定结果的配方或处方。它不应被视为一种宗教,其中非信徒或非遵从者将进入某个炼狱(尽管遵循该模式有助于避免维护地狱代码异味的炼狱)。
如果您想要一个如何帮助此特定模式的优秀示例,请尝试在ASP.Net中编写一些相当复杂的屏幕,然后在WPF或Silverlight中编写相同的内容,并注意差异。

编辑:

让我回答一些你的问题,希望能有所帮助……

在我看来,视图模型(视图模型)的作用是 UI 逻辑和视图状态。

视图模型不应该有任何 UI 逻辑或“视图状态”。对于此解释而言,我将视图状态定义为滚动位置、选定行索引、选定索引、窗口大小等。这些都不属于视图模型;例如 SelectedIndex 是特定于数据在 UI 中显示的方式(如果更改 DataGrid 的排序顺序,则 SelectedIndex 可能会更改,即使 SelectedItem 仍然相同)。在这种情况下,SelectedItem 可以绑定到视图模型,但 SelectedIndex 不应该绑定。
如果您需要跟踪 UI 会话类型信息,那么您应该想出一些通用的东西(例如,我曾经通过将重要内容保存到 KeyValuePair 列表中来保留视图状态),然后通过调用视图模型(通过其先前提到的接口)“保存”它。视图不知道数据是如何保存的,视图模型也不知道数据来自视图(它只是通过其接口公开了一个调用)。

而视图的作用是显示某些内容并同步视图模型(具有数据绑定代码)。

是的,视图的职责只是可视化地显示由视图模型呈现的数据。视图模型从模型中获取数据(模型负责进行数据库调用或WCF webservice调用,通常会通过“服务”来完成,但这是另一个讨论)。然后,视图模型可以对数据进行形状或操作,即它可能会获取所有客户的列表,但仅在公共属性中公开该列表的过滤版本(可能是当前客户),以便视图可以绑定到该属性。
如果要将数据转换为可视化内容(常见示例是将枚举值转换为颜色),则视图模型仍然只有枚举值,并且视图仍然绑定到该值,但视图还使用转换器将纯数据转换为可视表示。通过使用转换器,视图模型仍然避免了任何与UI相关的操作,而视图也避免了任何真正的逻辑。

1
您的答案对我来说非常有用。您可以让我确定一下这个问题吗?很抱歉打扰了。虽然我不是很理解,但在我看来,视图模型(视图的模型)的作用是UI逻辑和视图状态,而视图的作用是显示某些内容并同步视图模型(具有数据绑定代码)。将UI逻辑放在视图模型中比将UI逻辑放在视图中更合理。尽管如此,如果这很困难或不可能,而UI逻辑仅与视图相关,则将UI逻辑放在视图中。这正确吗? - Jin-Wook Chung
我认为你非常敏锐地指出了这个问题的答案。通过你的努力,我完全理解了。非常感谢。 - Jin-Wook Chung
4
@Aaron - 别误解我的意思。只有原教旨主义者才会要求视图的代码后台中不应该存在任何代码(请注意,我称他们为“原教旨主义者”,而非“纯粹主义者”)。我完全支持在代码后台中编写与视图/UI相关的代码,因为我经常看到人们将这些代码硬塞到VM中,因为他们认为必须这样做。有时候你需要做出艰难的决定,确定代码应该放在哪里,有时候代码会分别放在代码后台和VM中。 - slugster
2
@Slugster:我很高兴还有其他像我一样的人,我开始觉得只有我一个人了!干杯 :) - Aaron Murgatroyd
如果我在视图上放置一个按钮,通过代码更改视图的背景色,为什么要避免这样做呢?在我看来,这比XAML更易读。那么当视图模型检索数据并需要将焦点设置到选项卡视图时呢?这必须从VM调用视图。在我看来,大多数MVVM技术上更接近MVP。 - rollsch
显示剩余4条评论

11

MVVM可以完全分离代码和页面设计;程序员只需要关心编码,而设计师只需要关注设计。但是:

  1. 我从来没有见过任何使用Blend或理解XAML的设计师。
  2. 几乎所有XAML都是由程序员自己编写的。

5

代码后台本身并没有什么坏处。对于简单情况,使用它是可以的。然而,在许多情况下,UI逻辑可能会变得难以管理。将该逻辑封装在附加行为和视图模型中使我们能够隔离变量(并测试它们),从而更容易理解和维护。

如果可测试性是一个问题,您越多地将UI逻辑封装在视图模型和附加行为中,就越能够验证,而不必诉诸UI测试。(虽然它并不能完全消除UI测试的需求,但它提供了第一层验证,以便在进行更耗时/资源的UI测试之前进行。)


2
我认为引用部分指的是数据可视化的方式。我认为他们的意思是,你不应该在后台编写与数据显示相关的代码(例如类似于:label1.Text = ...)。使用绑定来完成这样的事情可以更容易地分离设计和代码(如果以后需要在名为“tbTest”的文本框中显示数据,你需要改变你的后台代码)。
他们并不是说你不能在后台编写任何代码 - 他们只是说,在理想的情况下,你只会对事件做出反应或处理无法以其他方式处理的数据。
至少这就是我从你引用的部分中所理解的。

我也认为您提出的将ViewState放在ViewModel中的答案是一个很好的观点。然而,我想知道为什么像AttachedCommandBehavior或者InvokeCommandAction这样的方法没有被尝试过。 - Jin-Wook Chung

-1

MVVM模式是强大的,但我认为它太“纯粹主义”了。我可以看到将代码处理所有命令和视图中的属性,而ViewModel则关注于对业务模型属性进行任何转换的好处。 其中一个好处是,如果您希望更改用户界面,例如从桌面更改为浏览器,则很可能只需更换View及其代码即可。

这只是我的想法!!


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