为啥要避免 WPF MVVM 模式中的代码隐藏?

Posted

技术标签:

【中文标题】为啥要避免 WPF MVVM 模式中的代码隐藏?【英文标题】:Why to avoid the codebehind in WPF MVVM pattern?为什么要避免 WPF MVVM 模式中的代码隐藏? 【发布时间】:2011-09-19 06:23:25 【问题描述】:

在文章WPF Apps With The Model-View-ViewModel Design Pattern,作者Josh Smith说:

(1) 在设计良好的 MVVM 架构中,大多数视图的代码隐藏应该是空的,或者最多只包含操作该视图中包含的控件和资源的代码。 (2) 有时还需要在 View 的代码隐藏中编写与 ViewModel 对象交互的代码,例如挂钩事件或调用原本很难从 ViewModel 本身调用的方法。

我的问题是,在(1)处,为什么空的代码隐藏被认为是一个设计良好的 MVVM。(听起来空的代码隐藏总是好的。)

编辑:我的问题如下,为什么像AttachedCommandBehaviorInvokeCommandAction 这样的方法试图避免代码隐藏编码。

让我更详细地解释一下。

就(1)而言,我认为来自AttachedCommandBehavior的情况如下。由于 Border 没有为 MouseRightButtonDown 实现 ICommandSource,因此您通常无法绑定事件和 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 Simth。

<Border MouseRightButtonDown ="Border_MouseRightButtonDown"/>

我认为使用上面的代码隐藏还不错,因为它们之间的区别仅在于绑定命令或添加事件处理程序的位置。

您对此有何看法?

【问题讨论】:

我认为,如果 Border_MouseRightButtonDown 不触发视图模型上的任何操作,并且不修改它的状态,那是完全可以的。在 ViewModel 中放置大量 UI 代码最终会将后面的代码转移到 ViewModel。 【参考方案1】:

MVVM 模式很强大,但我觉得它也很“纯粹”。我可以看到好处是让背后的代码处理视图中的所有命令和属性,而 ViewModel 则关注到业务模型属性的任何转换。 这样做的一个好处是,如果您希望更改用户 UI,可能从桌面更改为浏览器,很可能只是替换 View 及其背后的代码。

只是我的想法!!

【讨论】:

【参考方案2】:

为什么空的代码隐藏被认为是一个设计良好的 MVVM

拥有一个仅包含在其构造函数中调用 InitializeComponent() 的代码隐藏文件意味着您已经实现了纯度 - 您的代码隐藏中的逻辑绝对为零。您没有使用任何应属于视图模型或模型的代码污染您的视图。这意味着几件事:

viewmodel(和模型)更容易单独测试 您已达到良好的松散耦合水平,从维护和可扩展性的角度来看,这具有极好的优势

当你必须改变你的 UI 时,好处真的变得很明显,即你从使用 ListView 切换到 DataGrid,或者你从使用标准 Microsoft 控件更改为使用其他供应商的控件。

如上所述,有时无法避免代码隐藏文件中的一些代码。您应该确保您拥有的代码纯粹是与 UI 相关的。例如,如果您有 ComboA 和 ComboB,并且设置了 ComboB 以响应 ComboA 中的选择,那么从视图中设置 ComboB 的 SelectedIndex 就可以了,但设置 Items 或 ComboB 的 SelectedItem 则不行 - 这些属性是两者都与数据相关,并且应该通过绑定到视图模型来指定。 SelectedIndex 属性直接与视觉相关,在某种程度上独立于实际数据(并且与视图模型无关)。

如果您确实从视图中的代码隐藏访问视图模型,您应该尝试通过接口进行访问。这意味着您的视图模型作为接口被注入或提供给视图。 (请注意,绑定子系统不知道也不关心接口,它会继续以正常方式绑定。这样可以实现更好的代码,减少紧密耦合)。按照我的编码方式,viewmodel 不知道 view 的存在,而 view 只知道 viewmodel 作为一个接口。

但要记住的一点是,MVVM 是一种模式,而模式只是一个recipe or prescription,用于在特定情况下实现特定结果。它不应该被视为一种宗教,非信徒或不服从者将进入炼狱(尽管如果你想避免maintenance hell 和code smell 的炼狱,坚持这种模式是好的)。

如果您想要一个很好的例子来说明这种特定模式如何提供帮助,请尝试在 ASP.Net 中编写一些相当复杂的屏幕,然后在 WPF 或 Silverlight 中编写相同的屏幕,并注意区别。


编辑:

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

在我看来,视图模型的(视图模型)角色具有 UI 逻辑和视图状态

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

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

是的,视图的职责只是直观地显示视图模型呈现的数据。视图模型从模型中获取数据(模型负责进行数据库调用或 WCF Web 服务调用,这通常是通过“服务”完成的,但这是另一回事了)。然后,视图模型可以塑造或操作数据,即它可以获得所有客户的列表,但仅在视图可以绑定到的公共属性中公开该列表的过滤版本(可能是当前客户)。 如果要将数据处理成可视化的东西(一个常见的例子是将枚举值转换为颜色),那么视图模型仍然只有枚举值,并且视图仍然绑定到该值,但视图还 uses a converter 将纯数据转换为可视化表示。通过使用转换器,视图模型仍然避免了做任何与 UI 相关的事情,并且视图避免了任何真正的逻辑。

【讨论】:

您的回答对我来说很棒。你能让我保证这个问题吗?很抱歉打扰了。虽然我理解的不是很深,但是在我看来,viewmodel(视图模型)的角色有UI逻辑和视图状态,视图的作用是显示一些内容和同步视图模型(有数据绑定代码)。 UI 逻辑在视图模型中比 UI 逻辑在视图中更合理。尽管如此,如果它很难或不可能并且 UI 逻辑只与视图相关,那么 UI 逻辑将在视图中。这是对的吗? @jwJung,我已经扩展了我的答案,看看它是否有帮助。 我认为你非常尖锐地指出了这个问题的答案。通过你的努力,我完全理解。非常感谢。 @Aaron - 不要误会我的意思。只有原教旨主义者规定视图背后的代码中应该有零代码(注意我称他们为 fundamentalists,而不是 purists)。我完全支持后面代码中与视图/UI 相关的代码,因为我经常看到代码被塞进 VM,因为人们认为他们必须这样做。有时,代码应该去哪里是一个艰难的决定,有时代码会在后面的代码和虚拟机之间拆分。 @Slugster:我很高兴还有其他人和我一样,我开始认为我是唯一的人!干杯:)【参考方案3】:

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

    我从未见过任何使用 Blend 或了解 XAML 的设计师。 几乎所有的 XAML 都是由 coder 自己编写的。

【讨论】:

【参考方案4】:

代码隐藏本身并没有什么坏处。对于简单的情况,拥有它就可以了。但是,在许多情况下,UI 逻辑可能难以管理。将该逻辑封装在附加的行为和视图模型中,可以让我们隔离变量(并测试它们),以便更容易理解和维护。

如果可测试性是一个问题,您可以在视图模型和附加行为中封装的 UI 逻辑越多,您就越能够在不诉诸 UI 测试的情况下进行验证。 (虽然它并没有完全消除对 UI 测试的需求,但它确实在进行 UI 测试之前提供了第一级验证,这将更加耗费时间/资源。

【讨论】:

【参考方案5】:

我认为引用的部分是指数据的可视化方式。我认为他们的意思是你不应该在后面的代码中编写代码,例如,与数据的显示方式或位置相关(例如:label1.Text = ...)。使用绑定做类似的事情可以更容易地分离设计和代码(如果您需要在更高版本中将数据显示在名为“tbTest”的文本框中会发生什么?您必须更改后面的代码)。

他们并不是说您不应该在代码中包含任何代码 - 他们只是说在理想世界中,您只会对无法处理的事件或处理数据做出反应。

至少我从你引用的部分中是这样理解的。

【讨论】:

我也认为你的视图状态应该放在视图模型中的答案是好的。但是,为什么尝试像AttachedCommandBehaviorInvokeCommandAction 这样的方法是我的观点。

以上是关于为啥要避免 WPF MVVM 模式中的代码隐藏?的主要内容,如果未能解决你的问题,请参考以下文章

使用 MVVM 模式在 WPF DataGrid 中显示/隐藏行功能

MVVM设计模式和在WPF中的实现 事件绑定

MVVM WPF:为啥在运行应用程序时更新文本框中的文本后模型中的属性始终为空?

WPF自学入门WPF MVVM模式Command命令

WPF MVVM从入门到精通1:MVVM模式简介

WPF MVVM模式如何控制DataGrid的列隐藏和显示