【发布时间】:2014-11-10 15:55:56
【问题描述】:
一旦我发现了 MVVM,我就非常喜欢它。绑定、将视图与逻辑分离、可测试性等的整个概念非常令人鼓舞。对于背后的混乱、永无止境的代码来说,这是一个不错的选择。比我了解到有可以绑定的命令,我一开始也很喜欢。
使用 MVVM 编写了几个控件后,我发现我的视图模型开始看起来或多或少像背后的代码。完整的命令几乎完全完成了事件处理程序之前在代码中所做的事情。
让我举几个例子。
有一个带有“详细信息”按钮的控件可以打开另一个窗口。
[方法 1] 你可以做的第一件事(也是最糟糕的)是在你的命令中调用这样的东西:
new DetailsWindow().ShowDialog();
这使得视图模型强烈引用表示层 - 丑陋。
[方法 2] 让我们使用弱引用来解决这个问题,并创建类似 IDialogService 的东西。我们可以注入一个简单的实现来创建和打开窗口。现在我们摆脱了对表示层的强引用,命令可以如下所示:
_dialogService.ShowDetailsWindow();
我仍然不喜欢这种方法。对我来说,感觉视图模型不应该决定是否显示窗口。它应该提供和处理数据。
[方法 3] 将视图模型与表示层完全分离的优雅方法是注入命令本身。比视图模型不知道表示层。它只会执行注入的动作——不管它是什么。
问题 1:
哪种方法最好?我猜数字 3 是赢家。
问题 2:
这甚至应该成为视图模型的一部分吗?我认为它不应该因为它似乎是表示层的关注点。也许最好将它放在代码后面的简单事件处理程序中?
第二个例子更复杂。在同一个控件中,我们有一个“删除”按钮。它应该打开一个对话框,要求用户确认,如果他说“是”,它应该删除一些东西。在这种情况下,将它放在视图模型中更有意义,因为它确实会影响数据。
问题 3
这个案子对我来说是最棘手的。我不能使用我最喜欢的方法编号 3,因为我必须显示一个作为表示层工作的对话框,但 另外我必须根据对话框的具体情况执行一些逻辑结果 - 另一方面是视图模型的工作。这里最好的方法是什么?
请记住,我真的不想使用方法 1 和 2。我希望视图模型是干净的,并且不知道与表示层相关的任何内容——即使是弱引用也不知道。
我想到的一件事是将视图模型层分成两层。比如:
视图 --> 表示视图模型 --> 逻辑视图模型
- 演示视图模型
- 用作控件的上下文
- 包含逻辑视图模型作为直接绑定的公共属性
- 使用方法编号 2 - 现在可以接受,因为整个班级都是为了执行与演示相关的操作
- 逻辑视图模型
- 它是“免费的”
- 引用专门的逻辑服务
- 某些命令可以直接绑定到视图
- 某些命令可以由拥有它的表示视图模型执行
也许这是正确的方法?
[编辑]
针对 cmets 中关于使用框架的建议:
使用框架肯定会更容易处理窗口,但这不是问题的解决方案。我根本不希望“逻辑视图模型”处理窗口,甚至在框架的帮助下也不行。参考我最后建议的方法,我仍然会将它放在“演示视图模型”中
【问题讨论】:
-
这不是问题的直接答案,但您应该考虑使用 MVVM 框架(Caliburn.Micro、MVVM Light),因为他们已经为您完成了很多繁琐的工作,比如对话框或导航。例如,Caliburn.Micro 有
IWindowManager接口,可以将指定的视图模型显示为常规或模态窗口。这意味着您只能在视图模型上进行操作。 -
如果不使用第 3 方框架,我会使用
_navigationService.Show(SomeDialogViewModel)之类的东西,其中SomeDialogViewModel包含对话框需要显示的所有数据,可能还有一些标志来标识它应该是显示为对话框。然后是_navigationService的工作来确定如何以及在何处显示视图模型 -
为了呼应@Patryk 所说的,我发现Caliburn.Micro 非常适合这个。它按照约定解析适当的 Window 或 UserControl,然后允许您检查 ViewModel 实例以确定其在对话框关闭后的状态。您可以推出自己的实现;主要挑战是解决正确的视图。有多种方法可以做到这一点(基于约定、属性、注册映射的方法调用等)
-
IMO 最优雅的解决方案是,当执行命令时,VM 应该只引发一个事件并忘记它,并且任何订阅者都应该根据 VM 命令执行时应该执行的操作来处理该事件执行...