MVVM: 如何处理嵌套ViewModels之间的交互?

18
我一直在尝试使用经常提到的MVVM模式,并且在某些情况下很难定义清晰的边界。在我的应用程序中,我有一个对话框,允许我创建一个控制器的连接。对话框有一个ViewModel类,足够简单。但是,该对话框还托管一个附加控件(由ContentTemplateSelector选择),具体取决于被连接的特定类型的控制器。此控件具有其自己的ViewModel。
我遇到的问题是当我按 OK 关闭对话框时,我需要实际上创建请求的连接,这需要在内部 Controller-specific ViewModel 类中捕获的信息。简单地让所有Controller-specific ViewModel类实现构造连接的公共接口似乎是很诱人的,但是内部ViewModel真的应该负责这个构造吗?
我的一般问题是:是否有任何通常接受的设计模式来管理ViewModel之间的交互,特别是在“父”VM需要帮助“子”VM才能知道要做什么的情况下?
编辑:
我想出了一个比我最初想象的更干净的设计,但我仍然不确定这是否是正确的方法。我有一些后端服务,允许 ContentTemplateSelector 查看 Controller 实例并凭空找到一个控件以显示连接生成器。困扰我的是,我的顶级ViewModel将不得不查看生成的控件的 DataContext 并将其转换为适当的接口,这似乎是个坏主意(为什么 View 的 DataContext 与创建连接有任何关系?)
我最终得到了类似以下内容的东西(简化):
public interface IController
{
    IControllerConnectionBuilder CreateConnectionBuilder();
}

public interface IControllerConnectionBuilder
{
    ControllerConnection BuildConnection();
}

我的内部 ViewModel 类实现了 IControllerConnectionBuilder 接口,控制器返回内部 ViewModel。然后,顶层 ViewModel 可以通过伪魔法机制来可视化这个 IControllerConnectionBuilder,至少现在我的顶层 ViewModel 不需要知道细节(它甚至不知道或关心可视化控件是否使用 ViewModel),但是我的内部 ViewModel 执行建立这一过程仍然让我有些困扰。

如果有方法可以进一步简化此过程,欢迎分享更多想法。对于 ViewModel 应该拥有多少责任,我仍然不太清楚。


2
在我的工作中,我们经常问自己这种类型的问题。你表达得非常好,希望你能在这里得到一些有用的反馈。 - Steve Danner
2
谢天谢地这只是一个个人项目,所以我有探索不同设计的奢侈。我们的团队还没有采用WPF或MVVM,因为在当前阶段,它们的开销和笨拙是无法接受的。我坚信一旦我们学会如何使用它,这项技术将在生产力方面带来巨大的回报,但这是一种如此根本性的转变,很难知道在设计中应该画哪些界限。 - Dan Bryant
3个回答

3
我认为您希望让顶层ViewModel意识到NestedViewModel的存在,从分层的角度来看这是有意义的,母视图包含子视图。
在我看来,您的直觉是正确的,嵌套的ViewModel不应该公开由顶层用户操作引发的行为。相反,顶层ViewModel应该为其关联的视图提供行为。
但是,我建议将连接构建的责任移至ICommand中,并通过顶层ViewModel公开此命令。然后,您可以将主对话框上的“确定”按钮绑定到此命令,并且命令只需委托给顶层ViewModel,例如,在执行时调用ViewModel.CreateConnection()
然后,您的嵌套控件的责任纯粹是收集和公开数据给其NestedViewModel,以供包含的ViewModel使用。在需要输入相同信息(如果有)的不同上下文中,它在理论上更具可重用性——比如说,您想要重用它来编辑已创建的连接。
唯一的问题是,如果不同类型的NestedViewModel公开了完全不同的数据集合。
例如,一个公开HostNamePort作为属性,另一个公开UserNamePassword
在这种情况下,您可能需要做一些基础工作,以使顶层ViewModel.CreateConnection()仍然以干净的方式工作。虽然如果您只有少量的嵌套控件类型,那么这可能不值得努力,一个简单的NestedViewModel类型检查和转换就足够了。
这听起来可行吗?

这里的挑战在于外部ViewModel在编译时并不知道其InnerViewModel的类型。控制器作为应用程序的扩展在运行时被引入,并通过自定义DataTemplateSelector进行一些类型发现来连接特定的嵌套控件。我知道DataContext将隐式成为特定的嵌套ViewModel,但是检查DataContext并尝试将其转换为共享接口对我来说感觉很麻烦。 - Dan Bryant

3

在视图模型之间进行交互的一个有效选项是直接绑定到位于视图模型类之间的observer类。


谢谢提供链接;实际上我在其他协调方面使用了类似的模式,通过共享IMainViewModel服务来实现,该服务由我的MainViewModel实现。我认为进一步重构可能是有意义的,这样“共享”的功能就不会与主窗口的模型绑定在一起,而是成为一个MainObserver。 - Dan Bryant
1
这是一个有帮助的方法。我的设计还有点分裂,但我开始看到VM可以通过使用共享服务进行通信。感觉有点颠倒了,因为我习惯于“父母”知道其“子女”的一切。现在更多的是我的类说“我需要完成这个任务”,然后我几乎什么也不知道的应用程序的某个部分站出来替我处理它。 - Dan Bryant

0
我最近尝试使用Unity(微软企业库)进行依赖注入的实验。如果要使用完全定义了视图模型之间需要了解的接口,这可能是一个可行的方法。MEF是我所知道的另一个依赖注入的选择。
希望对你有帮助。

谢谢,实际上我在这个应用程序中使用了MEF,它在允许UI的非常丰富的可扩展性方面帮了我很多。正是这种可扩展性造成了设计上的挑战,因为现在UI托管的控件几乎完全不知道。我实际上已经想出了一种更清晰的方法来做到这一点,一旦今天回家后,我将详细说明。 - Dan Bryant

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