【问题标题】:ViewModels that talk to each other without a Framework无需框架即可相互通信的 ViewModel
【发布时间】:2013-05-06 17:15:19
【问题描述】:

简介

我有一个在运行时导入实验室仪器数据的应用程序。导入此数据,然后以最终用户根据其测试要求设置的时间间隔显示在ListView 中。当他们关注的ListView 中出现感兴趣的值时,他们会按下“开始”按钮,应用程序开始对该数据和后续数据执行计算,直到按下“停止”按钮。因此,屏幕左侧是一个视图,用于显示导入的数据,右侧是另一个视图,用于查看计算和显示的值和统计数据。

当前代码

显示导入数据的 ListView 的视图是 ImportProcessView.xaml,它将其DataContext 设置为ImportProcessViewModel.cs。我刚刚介绍的 VM 有一个属性ObservableCollection<IrData>,我刚刚描述的 ListView 绑定到该属性。现在到有趣的部分...

ImportProcessView 有一个 ContentControl 动态设置它的内容,一个 UserControl 表示特定于最终用户选择的阶段类型的控件和字段。

<StackPanel Background="White" Margin="5">
    <ContentControl Content="{Binding CurrentPhaseView}"/>
</StackPanel>

共有三个PhaseViews,每个都在自己的用户控件中,每个都将其DataContext 设置为ImportProcessViewModel。结果,我得到了一些严重的 VM 膨胀,达到 2000 行。荒谬的。我知道。膨胀的原因是ImporProcessViewModel 通过三个 PhaseView 中的每一个的属性来维护状态,不仅如此,还包含执行计算的方法,其数据存储并显示在这些“PhaseView”中。

我正在努力实现的目标

显然,在ImportProcessViewModel 变得更加笨拙之前,我需要将其分解,以便每个 PhaseView 都有自己的 ViewModel,而且为了 ObservableCollection 施加的依赖关系,每个 ViewModel 都保持与 ImportProcessViewModel 的关系IrData.

研发

我对 ViewModel 之间的通信进行了研究,但大多数 results 涉及使用特定 MVVM 框架编写的应用程序。我没有使用框架,在项目的这一点上,重构它以开始使用框架为时已晚。

然而,我确实找到了这个article,'hbarck' 提供的答案建议像组合这样简单的东西来实现我想要的结果,但由于我对 DataTemplates 没有太多经验,所以我不明白什么意思是当他/她建议将“UserControl 的 ViewModel 作为主 ViewModel 上的属性,并将 ContentControl 绑定到该属性,然后通过 DataTemplate 实例化 View(即 UserControl)”

具体来说,我不明白 “将 ContentControl 绑定到此属性,然后 通过 DataTemplate 实例化视图” 是什么意思。

有人可以通过代码示例阐明在此示例的上下文中通过 DataTemplate 实例化视图是什么意思吗?

此外,这是一个好方法吗(正如“hbarck”所建议的那样)?

正如所见,我已经将 ContentControl 的 Content 属性设置为要实例化的阶段视图。我只是不知道涉及 DataTemplate 会是什么样子。

【问题讨论】:

标签: c# wpf mvvm


【解决方案1】:

我不明白他/她建议揭露“ UserControl 的 ViewModel 作为主 ViewModel 上的一个属性,并绑定 一个 ContentControl 到这个属性,然后实例化 通过 DataTemplate 查看(即 UserControl)”

DataTemplate 允许您指定视图(例如用户控件)和视图模型之间的关系。

<DataTemplate DataType="{x:Type myApp:MyViewModel}">
    <myApp:MyUserControl />
</DataTemplate>

这告诉ContentPresenter 在其内容属性设置为MyViewModel 的实例时显示MyUserControl。视图模型将用作用户控件DataContext。通常,DataTemplate 会添加到您的应用程序资源中。

该答案的作者所说的是,您可以拥有一个 viewModel,它具有另一个 viewModel 类型的属性,该属性绑定到 ContentPresenter 的 Content 属性。

<ContentPresenter Content="{Binding ParentViewModel.ChildViewModelProperty}"/>

如果您有一个 DataTemplate 来指定您的 ChildViewModel 和您的用户控件之间的关系,WPF 会自动将用户控件加载到您的视图中。

This answer我提供给另一个问题也可能会给你一些帮助。

我需要将其分解,以便每个 PhaseView 都有自己的 ViewModel, 但也使得每个 ViewModel 都与 ImportProcessViewModel。

这将允许您将视图模型分解为更小、更易于管理的视图模型,这些视图模型可以照顾自己。这会给您留下视图模型之间的通信问题。

如果您按照建议嵌套视图模型,那么您的子视图模型可以公开父视图模型可以绑定的事件,以便在发生变化时通知它。像这样的:

public class ParentViewModel // Derive from some viewModel base that implements INPC
{
    public ParentViewModel()
    {
         childViewModel = new ChildViewModel();
         childViewModel.SomeEvent += someEventHandler;
         // Don't forget to un-subscribe from the event at some point...
    }

    private void SomeEventHandler(object sender, MyArgs args)
    {
        // Update your calculations from here...
    }
}

这很简单,不需要任何额外的框架。有些人可能会反对这种方法,但它是一个有效的解决方案。缺点是视图模型必须知道彼此的存在才能订阅事件,因此最终可能会紧密耦合。不过,您可以使用标准的面向对象设计原则来解决这个问题(即从接口派生您的子 viewModel,以便父级只知道接口而不知道实现)。

如果您真的想进行松耦合通信,那么您需要使用某种事件聚合或消息总线系统。这类似于上面的方法,只是有一个对象位于视图模型之间并充当中介,因此视图模型不必知道彼此的存在。我的answer here 提供了更多信息。

已有可用的解决方案,但这需要采用额外的框架。我建议使用Josh Smiths MVVM foundation,因为它非常简单,而且您只需要使用一个类即可。

【讨论】:

  • 很好的答案。我仍在消化其中的大部分内容。首先,当您提到“嵌套虚拟机”时,您能详细说明一下吗?这是基于继承、组合还是其他?我问是因为“父母”和“孩子”这两个词。
  • 在我的回答中,我说的是视图模型的组成。父级通过属性公开子级。
  • 在您的代码中,您声明&lt;ContentPresenter Content="{Binding ParentViewModel.ChildViewModelProperty}"/&gt;。因为此代码将在 ParentView.xaml 上,所以它不只是 &lt;ContentPresenter Content="{Binding ChildViewModelProperty}"/&gt;,因为 ParentView.xaml 将其 DataContext 设置为 ParentViewModel?
【解决方案2】:

虽然本杰明的回答非常详尽且非常有帮助,但我想澄清一下我在另一篇文章中写的内容如何适用于您的问题:

  • 对于不同的阶段,您将拥有三个不同的 PhaseViewModel-Class,它们可能派生自一个公共基类,比如 PhaseVMBase。
  • 您可能拥有 CurrentPhaseVM 属性,而不是 CurrentPhaseView 属性。这将是 Object 或 PhaseVMBase 类型,并返回三个 PhaseViewModel 类之一,具体取决于用户在主 ViewModel 中选择的内容。
  • PhaseVMBase 将有一个 UpdateData 方法,当主 ViewModel 收到应由阶段视图处理的新数据时,将调用该方法。主 ViewModel 会在当前发生的任何 CurrentPhaseVM 上调用此方法。 PhaseViewModels 将实现 INotifyPropertyChanged,因此由于 UpdateData 导致的更改将对绑定控件可见。
  • 您的 DataTemplates 将在主视图的资源中声明,例如主窗口,

像这样:

<DataTemplate DataType="{x:Type my:Phase1VM}">
  <my:Phase1View/>
</DataTemplate>
<DataTemplate DataType="{x:Type my:Phase2VM}">
  <my:Phase2View/>
</DataTemplate>
<DataTemplate DataType="{x:Type my:Phase3VM}">
  <my:Phase3View/>
</DataTemplate>

请注意,没有 x:Key,只有 DataType 值。如果这样声明,当要求 WPF 分别显示 Phase1VM、Phase2VM 或 Phase3VM 类型的对象时,WPF 将选择适当的 DataTemplate。 Phase1View、Phase2View 和 Phase3View 将是 UserControls,它们知道如何显示不同的 ViewModel。他们不会自己实例化他们的 ViewModel,但希望他们的 DataContext 从外部设置为他们各自 ViewModel 的实例。

假设应该显示阶段视图的 ContentControl 在主视图中声明,并且 DataContext 将是主 ViewModel,您可以像这样声明 ContentControl:

<ContentControl Content="{Binding CurrentPhaseVM}"/>

根据 CurrentPhaseVM 的实际类型,这将选择三个 DataTemplate 之一,并显示相应的 UserControl。 UserControl 的 DataContext 将自动成为 ContentControl 的内容,因为那将是导致选择 DataTemplate 的对象。

编辑:列表和代码格式不能一起使用,似乎...

【讨论】:

  • 这真是太有帮助了。我很高兴你看到我的帖子并选择回答。 +1
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-13
  • 2021-07-22
  • 2017-04-18
  • 1970-01-01
  • 1970-01-01
  • 2015-03-12
相关资源
最近更新 更多