6

介绍

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

当前代码

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

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

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

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

我想要达到的目标

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

研发

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

但是,我确实找到了这篇文章,并且“hbarck”提供的答案提出了一些简单的方法,例如组合来实现我想要的结果,但是由于我对 DataTemplates 没有太多经验,所以我不明白当他/她建议将“UserControl 的 ViewModel 作为主 ViewModel 上的属性公开,并将 ContentControl 绑定到该属性,然后通过 DataTemplate 实例化 View(即 UserControl)”

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

有人可以通过代码示例阐明在此示例的上下文中通过 DataTemplate 实例化视图的含义吗?

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

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

4

2 回答 2

5

我不明白他/她建议将“UserControl 的 ViewModel 作为主 ViewModel 上的属性,并将 ContentControl 绑定到该属性,然后通过 DataTemplate 实例化 View(即 UserControl)”时的意思

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

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

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

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

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

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

我为另一个问题提供的这个答案也可能为您提供一些帮助。

我需要将其分解,以便每个 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...
    }
}

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

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

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

于 2013-05-06T17:58:26.077 回答
4

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

  • 对于不同的阶段,您将拥有三个不同的 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 的对象。

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

于 2013-05-06T18:52:45.503 回答