为啥要避免 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。(听起来空的代码隐藏总是好的。)
编辑:我的问题如下,为什么像AttachedCommandBehavior
或InvokeCommandAction
这样的方法试图避免代码隐藏编码。
让我更详细地解释一下。
就(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”的文本框中会发生什么?您必须更改后面的代码)。
他们并不是说您不应该在代码中包含任何代码 - 他们只是说在理想世界中,您只会对无法处理的事件或处理数据做出反应。
至少我从你引用的部分中是这样理解的。
【讨论】:
我也认为你的视图状态应该放在视图模型中的答案是好的。但是,为什么尝试像AttachedCommandBehavior
或InvokeCommandAction
这样的方法是我的观点。以上是关于为啥要避免 WPF MVVM 模式中的代码隐藏?的主要内容,如果未能解决你的问题,请参考以下文章
使用 MVVM 模式在 WPF DataGrid 中显示/隐藏行功能