MVVM – 用户控制相互通信的理想方式是什么

我有一个用户控件,其中包含其他几个用户控件。 我正在使用MVVM。 每个用户控件都有一个相应的VM。 这些用户控件如何相互发送信息? 我想避免在后面的xaml代码中编写任何代码。 特别是我对控件(主用户控件内部)如何相互通信以及如何与容器用户控件进行通信感兴趣。

编辑 :我知道使用事件代表将帮助我解决这个问题。 但是,我想避免在xaml代码隐藏中编写任何代码。

通常,最好尝试减少部件之间的通信量,因为每次两个用户控件彼此“对话”时,您就会在它们之间引入依赖关系。

话虽如此,还有几件事需要考虑:

  • UserControls可以通过公开属性和使用DataBinding始终与其包含的控件“对话”。 这非常好,因为它在所有方面都保留了MVVM风格。
  • 包含控件可以使用属性将两个用户控件上的两个属性“链接”在一起,同样保留干净的边界

如果您确实需要更明确的沟通,有两种主要方法。

  1. 实现两个元素共有的服务,并使用dependency injection在运行时提供实现。 这使得控件可以与服务进行通信,从而可以使控件保持同步,但也可以将依赖性保持在最低限度。
  2. 使用某种forms的消息传递在控件之间传递消息。 许多MVVM框架采用这种方法,因为它将发送消息与接收消息分离,同样,将依赖性保持在最低限度。

你的概念问题在这里:

每个用户控件都有一个相应的VM。

为每个视图设置一个单独的ViewModel几乎违背了ViewModel的概念。 ViewModels不应该与视图一对一,否则它们只不过是美化的代码隐藏。

ViewModel捕获“当前用户界面状态”的概念 – 例如您所在的页面以及是否正在编辑 – 而不是“当前数据值”。

要真正获得MV-VM的好处,请根据需要状态的不同项确定所使用的ViewModel类的数量。 例如,如果您有一个项目列表,每个项目都可以以3种状态显示,则每个项目需要一个VM。 相反,如果您有三个视图,所有视图都根据常用设置以3种不同方式显示数据,则应在单个VM中捕获公共设置。

一旦您构建了ViewModel以反映手头任务的要求,您通常会发现不需要也不希望在视图之间传递状态。 如果有这样的需求,最好的办法是重新评估您的ViewModel设计,看看共享的ViewModel是否可以从少量额外的状态信息中受益。

有时,应用程序的复杂性决定了对同一个模型对象使用多个ViewModel。 在这种情况下,ViewModel可以保持对公共状态对象的引用。

这有许多不同的机制,但您应首先了解此通信属于您的体系结构的哪一层。

MVVM框架的一个目的是可以在同一个视图模型上创建不同的视图。 这些用户控件是否仅在您当前正在实现的视图中相互通信,或者他们是否必须在其他可能的视图中相互通信? 在后一种情况下,您希望在视图级别下面的视图模型或模型本身中实现它。

第一种情况的示例可能是您的应用程序在非常小的显示器表面上运行。 也许您的用户控件必须竞争视觉空间。 如果用户单击一个用户控件以使其最大化,则其他用户控件必须最小化。 这与viewmodel无关,它只是对技术的适应。

或者,您可能有不同的视图模型具有不同的用户控件,可以在不更改模型的情况下进行操作。 这方面的一个例子可能是导航。 您有一个事物列表,以及一个详细信息窗格,其中包含连接到列表中所选项目的字段和命令按钮。 您可能希望unit testing为哪些项启用哪些按钮的逻辑。 只有按下按钮命令或更改字段时,模型才会关注您正在查看的项目。

这种沟通的需要甚至可能在模型本身。 也许您已经更新了非规范化数据,因为其他数据已更改。 然后,由于模型中的变化波动,运行中的各种视图模型必须更改。

所以,总结一下:“这取决于……”

我认为最好的解决方案是使用Publisher / Subscriber模式。 每个控件都会注册一些事件,并将删除操作附加到其他控件公开的事件中。

为了公开事件并附加到它们,您需要使用某种Mediator / EventBroker服务。 我在这里找到了一个很好的例子

在我看来,最好的方法是通过命令(路由命令/中继命令等)。

我想避免在后面的xaml代码中编写任何代码。

虽然这是一个值得称赞的目标,但你必须对此应用一些实用性,它不应该100%作为“你不应该”的规则应用。

您可以使用元素绑定在UI上的元素之间进行通信,因此假设您创建的用户控件公开了属性,则其他用户控件可以绑定到它。 您可以配置绑定,使用依赖属性而不是基本属性/实现INotifyPropertyChanged但理论上可行,但确实需要一些预先考虑以这种方式进行通信。

您可能会发现使用事件,代码和属性的组合比尝试纯粹的声明方式更容易,但理论上可行。

您可以在控件和命令之间共享一些View Model对象…

例如,您有一些主控件,其中包含另外两个控件。 并且您在主控件中有一些过滤function,但您希望允许用户在第一个子控件中设置filter的某些部分(如“完全filter”),在另一个子控件中设置filter的某些部分(如“快速filter”) “)。 您还希望能够从任何子控件开始过滤。 然后你可以使用这样的代码:

 public class MainControlViewModel : ObservableObject { public FirstControlViewModel firstControlViewModel; public SecondControlViewModel firstControlViewModel; public ICommand FilterCommand; public FilterSettings FilterSettings; public MainControlViewModel() { //... this.firstControlViewModel = new FirstControlViewModel(this.FilterSettings, this.FilterCommand); this.secondControlViewModel = new SecondControlViewModel(this.FilterSettings, this.FilterCommand); } } public class FirstControlViewModel : ObservableObject { //... } public class SecondControlViewModel : ObservableObject { //... } 

在主控件XAML中,您将子控件DataContext绑定到适当的View Models。 每当子控件改变滤波器设置或执行命令时,将通知其他子控制。

正如其他人所说,你有几个选择。

在用户控件上公开DepedencyProperties并绑定到这些属性在大多数情况下提供纯XAML解决方案,但可以引入一些UI依赖项,以便绑定看到彼此

另一个选项是在ViewModel之间发送消息的解耦消息传递模式。 我希望你的用户控件绑定到他们自己的VM上的属性,然后在该VM内部的属性更改它可以“发布”一条消息,通知其他“订阅者”发生了某些事情,他们可以对该消息作出反应,但是他们想要。

如果它有帮助,我有一篇关于这个主题的博客文章: http : //www.bradcunningham.net/2009/11/decoupled-viewmodel-messaging-part-1.html

如果你使用严格的MVVM,那么用户控件就是一个View,它应该只它的ViewModel“对话”,或者更确切地说, 绑定它。 由于您的ViewModel很可能已经实现了INotifyPropertyChanged,只要它们彼此具有引用,它们就可以使用PropertyChanged事件在属性更改时得到通知,或者它们可以调用方法(如果通过接口更好),以便与每个方法进行通信其他。