MVVM 中视图模型的交互

Posted

技术标签:

【中文标题】MVVM 中视图模型的交互【英文标题】:Interaction of view models in MVVM 【发布时间】:2012-12-30 23:10:12 【问题描述】:

我有一个遵循 MVVM 模式的 WPF 应用程序。到目前为止,该应用程序定义了两个视图和视图模型:

登录视图(模型) 项目视图(模型)

两个视图模型都需要访问其他视图模型的多个属性。

示例:LoginViewModel 有一个属性 ProjectListProjectsViewModel 也需要访问此属性。

这只是一个简单的例子。后面会有几个UserControls,都需要相互交互。

创建一个将所有UserControls(视图)设置为DataContext 的巨大视图模型会更好吗?如果不是,那么所有不同的视图模型如何相互交互?

备注: 这个问题与this one 密切相关,但方法不同。

【问题讨论】:

【参考方案1】:

你绝对不应该制作一个巨大的“主视图模型”——这是一种与god object 相似的反模式。

这里的关键思想是您的视图模型不需要从其他视图模型访问多个属性;相反,所有视图模型都需要访问特定的信息

现在,您很可能在实例化时将此信息注入每个视图模型。不要这样做,而是为每个视图模型提供对 service 的引用——一个通过属性和/或方法公开此信息的应用程序模块。如果信息本质上非常简单,则可以以标量值的形式公开,如果比这更复杂,则可以以模型的形式公开。

大概是这样的

/--------------\
|  ViewModelA  |
|              | <=======\
|              |         |
\--------------/         |  <--- information is pulled      /================\
                         +=========[model]===[model]======  |    Service     |
/--------------\         |                                  \================/
|  ViewModelB  |         |
|              | <=======/
|              |
\--------------/

服务应在构造时将injected 放入视图模型中(手动或通过 DI 容器,如果您正在使用)。每个视图模型应该只需要对服务的引用和足够的信息来告诉服务它对哪个模型感兴趣;然后它将从服务中请求该模型,并基于此填充其“有趣”的属性。

这种做事方式比简单地构建视图模型对象并在外部设置其属性更复杂,但它可以让您的应用程序变得复杂而不会变得难以管理。

例如,如果有很多视图绑定在不同的视图模型上,并且这些视图模型以复杂的方式依赖于多个模型,那么合理的工作方式是:

    构造了一个视图/视图模型对 视图模型向服务请求它需要了解的任何模型;它订阅服务公开的事件,让订阅者知道模型已更改 通过数据绑定控件对模型执行更改 视图调用视图模型上的“保存”命令 作为响应,视图模型在服务上调用“保存此模型”方法 服务将信息持久化并发布“模型更改”事件(参见步骤 2) 对同一模型感兴趣的其他视图模型知道模型已更改并可以查询服务以了解其新状态

如果一切都通过服务路由,这是可能的。想象一下保持一切同步需要什么 - 应对的唯一方法是将所有信息放入一个巨大的视图模型中并从其他所有内容中引用它。丑陋。

【讨论】:

这听起来很合理。好的,现在我需要做的就是找出如何实现这样的服务:-) @RobertStrauch - 这就是程序员的生活。 如果每个 ViewModel 的属性不同,即以不同的方式呈现模型,则可以。但是,如果您有一个对所有 Viewmodel 都有效的属性?当模型改变时,你会为他们每个人重建它吗?接受经常使用分层视图模型。 @Natxo:当然,不管你的船是什么。如果你想看看this answer,我更详细地解释了一个完全一样的设置。它工作得很好,我记得很愉快。诚然,将所有内容连接在一起的代码相当多。 @Robert:没错。该服务按需创建模型(它甚至可能不拥有数据本身;通常它作为一个额外的抽象层位于数据提供者的前面)。【参考方案2】:

通常我会做以下 4 件事之一:

让我的 ViewModel 相互引用,并传递属性。例如,LoginViewModel 可能会在成功登录时设置ProjectsViewModel.ProjectList。如何实现这一点取决于您的 ViewModel 之间的关联方式以及逻辑连接点的位置。

创建一个ApplicationViewModel,它管理诸如当前页面和当前用户或当前项目列表等应用程序范围的对象。它将处理将共享数据传输到需要它的 ViewModel。

使用某种事件系统在应用程序范围的属性更改时广播消息,并让任何感兴趣的 ViewModel 订阅以接收这些消息。该消息通常也包含新对象,因此订阅接收该消息类型的任何人都可以访问新对象。 (如果您有兴趣,我在我的博客文章Communication between ViewModels with MVVM 中有一个事件系统的简要总结)

根据其他选项是否更好,我可能会考虑创建一个单例来保存应用程序范围的数据。例如,如果用户在登录时设置并且可以从我的许多 ViewModel 访问,我可以创建一个单例来设置用户在第一次登录时,然后我的所有 ViewModel 都可以访问它。

我会做的一件事是制作一个包含所有可用数据的大型 ViewModel。 ViewModel 应该只包含特定于它的数据。

【讨论】:

采取第一个想法...您能否提供示例代码 sn-p 如何“相互”引用视图模型? @Robert 通常我只对紧密链接的 ViewModel 执行此操作,其中连接看起来很自然,例如包含对 SelectedCustomerViewModel 的引用的 AllCustomersViewModel。` 想知道为什么不将 MainViewModel 用于您描述的第二个选项中由 ApplicationViewModel 处理的功能? @DeanKuga 你可以,这取决于你的项目有多大,什么被认为是应用程序的一部分,什么被认为是主窗口的一部分 @Rachel 你说得对,如果 MainViewModel 是主窗口的 ViewModel,那么在单独的 ViewModel 中从中提取一些应用范围的项目是有意义的。这也促进了关注点的分离......【参考方案3】:

如果您使用某种依赖注入工具,您可以注入一个提供这些值的类,例如 IProjectService,它可以返回每个视图模型上的项目列表。

一个巨大的视图模型的想法听起来并不吸引人,视图模型之间通过访问彼此的公共属性的强耦合也没有。如果您想要视图模型之间的交互,例如添加项目时,请为您希望发生的每个事件使用发布/订阅模型。

【讨论】:

【参考方案4】:

您的模型应该包含项目列表之类的内容,或者让您能够访问它。 ViewModel 只是作为模型和视图之间的一个层,让您有机会将不同的模型对象组合在一起并将它们塑造成视图可以轻松显示的形状并处理来自视图的命令(修改模型以响应)。

【讨论】:

这个想法也在我脑海中浮现。但是,我有一个问题:如何在需要它的视图模型中访问相同的模型? 使模型对象构造函数在内部,然后创建一个可以根据请求传递对象的单例“ModelObjectAccessor”类。访问器可以创建一个缓存(如果使用依赖注入,实际上可以由 View 实现),以确保不会对数据库进行不必要的重复调用。【参考方案5】:

创建一个基本视图模型,其中保留所有公共属性,为 BaseViewModel 创建单例并将该单例属性添加到所有视图模型中。所有 Viewmodel 将同时同步。

【讨论】:

以上是关于MVVM 中视图模型的交互的主要内容,如果未能解决你的问题,请参考以下文章

谈MVVM

SPA和MVVM设计思想

MVVM MVC

SwiftUI 和 MVVM - 模型和视图模型之间的通信

MVC和MVVM

MVC和MVVM