MVVM - 用户控件相互交谈的理想方式是啥

Posted

技术标签:

【中文标题】MVVM - 用户控件相互交谈的理想方式是啥【英文标题】:MVVM - what is the ideal way for usercontrols to talk to each otherMVVM - 用户控件相互交谈的理想方式是什么 【发布时间】:2010-12-20 09:27:48 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

通常情况下,最好尽量减少部件之间的通信量,因为每次两个用户控件相互“交谈”时,您就会在它们之间引入依赖关系。

话虽如此,有几点需要考虑:

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

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

    实现两个元素共有的服务,并使用依赖注入在运行时提供实现。这让控件可以与服务通信,而服务又可以使控件保持同步,但也可以将依赖关系降至最低。 使用某种形式的消息传递控件之间的消息。许多 MVVM 框架采用这种方法,因为它将发送消息与接收消息分离,再次将依赖关系保持在最低限度。

【讨论】:

对于第 2 点:Josh Smith 的 MvvmFoundation 有一个“信使”类,它使用注册/通知机制在 VM 之间传递消息。它们被存储为弱引用以避免内存泄漏,并且工作得非常好! 是的,我还推荐 MVVM Foundation 中的 Messenger 类。很棒的东西。 是的——当我提到“消息传递消息”时,我就是这么想的。如果您不希望 DI 注入服务,这是一种有效的技术。【参考方案2】:

你的概念问题在这里:

每个用户控件都有一个对应的虚拟机。

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

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

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

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

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

【讨论】:

【参考方案3】:

对此有许多不同的机制,但您应该首先找出这种通信属于您架构的哪一层。

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

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

或者你可能有不同的视图模型和不同的用户控件,在不改变模型的情况下事情可以发生。这方面的一个例子可能是导航。您有一个内容列表,以及一个包含字段和命令按钮的详细信息窗格,这些字段和命令按钮连接到列表中的选定项目。您可能希望对哪些按钮启用哪些项目的逻辑进行单元测试。该模型不关心您正在查看的项目,仅在按下按钮命令或更改字段时。

这种沟通的需求甚至可能存在于模型本身中。也许你有非规范化的数据因为其他数据被改变而被更新。然后,由于模型变化的涟漪,正在运行的各种视图模型必须发生变化。

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

【讨论】:

【参考方案4】:

我认为最好的解决方案是使用发布者/订阅者模式。每个控件注册一些事件并将委托附加到其他控件公开的事件。

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

【讨论】:

你能详细解释一下吗?你的意思是使用 CLR 委托和事件? 我刚刚编辑了我的答案,使用 Marlon Grech 博客中的示例代码添加了演示文稿的链接。视图模型以发布者/订阅者模式进行通信(不需要后面的代码)。中介服务使用 CLR 委托来注册事件处理程序。【参考方案5】:

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

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

虽然这是一个值得称赞的目标,但您必须对此应用一点实用性,但不应将其 100% 应用为“您不应”类型的规则。

【讨论】:

codebetter.com/blogs/glenn.block/archive/2009/08/02/…【参考方案6】:

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

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

【讨论】:

【参考方案7】:

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

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

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 绑定到适当的视图模型。每当子控件更改过滤器设置或执行命令时,都会通知其他子控件。

【讨论】:

【参考方案8】:

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

在您的用户控件上公开 DepedencyProperties 并绑定到这些属性在大多数情况下提供了纯 XAML 解决方案,但可能会引入一些 UI 依赖项以使绑定相互看到

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

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

【讨论】:

【参考方案9】:

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

【讨论】:

所以你的意思是如果两个用户控件必须相互交谈,那么他们应该通过各自的虚拟机来完成吗?而且我认为我不太同意他们应该订阅彼此的 PropertyChanged 事件。 @Sandbox:如果你使用真正的 MVVM,那么 View 知道它的 ViewModel,但 View 不应该知道彼此,所以不,它们不应该直接与每个其他。如果您这样做,您将很难对视图间通信进行单元测试。您可以使用任何您希望 ViewModel 了解或相互通信的方法。 .NET 充满了选择。使用任何适合需要的东西。

以上是关于MVVM - 用户控件相互交谈的理想方式是啥的主要内容,如果未能解决你的问题,请参考以下文章

如何正确绑定到 MVVM 框架中用户控件的依赖属性

在 WPF MVVM 中添加多个用户控件的最佳控件?

WPF MVVM Lights - 具有不同参数的相同用户控件

MVVM Light - 用户控件作为视图

WPF MVVM 将用户控件绑定到主窗口视图模型

用户控件中的Mvvm light SimpleIoC