ViewModels在没有框架的情况下相互交谈

时间:2013-05-06 17:15:20

标签: c# wpf mvvm

简介

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

当前代码

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

ImportProcessViewContentControl动态设置其内容的UserControl,表示特定于最终用户选择的阶段类型的控件和字段。

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

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

我想要实现的目标

显然,在ImportProcessViewModel变得更加笨拙之前,我需要将其分解,以便每个PhaseView都有自己的ViewModel,但每个ViewModel都会维护一个与ImportProcessViewModel关系的关系,以便依赖于IrData的ObservableCollection。

的R&amp; d

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

然而,我确实发现了这个article并且'hbarck'提供的答案提示了一些简单的组合来实现我想要的结果,但是因为我没有太多使用DataTemplates的经验所以我没有了解他/她建议将“UserControl的ViewModel作为主ViewModel上的属性公开,并将ContentControl绑定到此属性,然后通过DataTemplate实例化View(即UserControl)”

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

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

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

可以看出,我已经将ContentControl的Content属性设置为要实例化的Phase View。我只是不知道DataTemplate会是什么样的。

2 个答案:

答案 0 :(得分:4)

  

我不明白当他/她建议揭露“的时候是什么意思   UserControl的ViewModel作为主ViewModel上的属性,并绑定   一个ContentControl到这个属性,然后实例化   通过DataTemplate“

查看(即UserControl)

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

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

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

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

ContentPresenter

假设您<ContentPresenter Content="{Binding ParentViewModel.ChildViewModelProperty}"/> 指定了DataTemplate与您的用户控件之间的关系,WPF会自动将用户控件加载到您的视图中。

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

  

我需要将其分解,以便每个PhaseView都有自己的ViewModel,   而且每个ViewModel都保持一种关系   ImportProcessViewModel。

这将允许您将viewModel分解为更小,更易于管理的自定义视图模型。这将使您在viewModels之间进行通信时遇到问题。

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

ChildViewModel

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

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

已有预先存在的解决方案,但这需要采用额外的框架。我建议使用Josh Smiths MVVM foundation因为它很简单,你只需要使用一个类。

答案 1 :(得分:3)

虽然本杰明的答案非常精细且非常有用,但我想澄清一下我在其他帖子中写的内容如何适用于你的问题:

  • 你有不同阶段的三个不同的PhaseViewModel-Classes,可能来自一个公共基类,比方说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值。如果这样声明,当被要求分别显示Phase1VM,Phase2VM或Phase3VM类型的对象时,WPF将选择适当的DataTemplate。 Phase1View,Phase2View和Phase3View将是UserControls,它将知道如何显示不同的ViewModel。他们不会自己实例化他们的ViewModel,但期望他们的DataContext从外部设置为各自ViewModel的实例。

假设应在主视图中声明应显示阶段视图的ContentControl,并且DataContext将是主ViewModel,您将声明ContentControl,如下所示:

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

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

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