在没有框架的情况下相互通信的视图模型
本文关键字:互通 通信 视图 模型 情况下 框架 | 更新日期: 2023-09-27 18:12:32
简介
我有一个应用程序,可以在运行时导入实验室仪器数据。此数据被导入,然后按照最终用户根据其测试要求设置的时间间隔显示在ListView
中。当他们监视的此ListView
中出现感兴趣的值时,他们按"开始"按钮,应用程序开始对该基准和后续数据执行计算,直到按下"停止"按钮。因此,屏幕左侧是用于显示导入数据的视图,右侧是另一个视图,用于在计算和显示值和统计信息时查看它们。
当前代码
显示数据导入到的列表视图的视图是 ImportProcessView.xaml,它将其DataContext
设置为 ImportProcessViewModel.cs
。我刚刚介绍的 VM 有一个属性ObservableCollection<IrData>
,列表视图(我也刚刚介绍了(绑定到该属性。现在进入有趣的部分...
ImportProcessView
有一个ContentControl
,该动态设置其内容,UserTControl表示特定于最终用户选择的阶段类型的控件和字段。
<StackPanel Background="White" Margin="5">
<ContentControl Content="{Binding CurrentPhaseView}"/>
</StackPanel>
有三个PhaseViews
,每个都有自己的用户控件,每个设置都DataContext
到ImportProcessViewModel
。结果,我得到了一些严重的 VM 膨胀到 2000 行。荒谬。我知道。膨胀的原因是因为ImporProcessViewModel
通过三个相位视图中每个的属性来维护状态,不仅如此,还包含用于执行计算的方法,其数据存储并显示在这些"相位视图"中。
我正在努力实现的目标
显然,在ImportProcessViewModel
变得更加笨拙之前,我需要将其分解,以便每个PhaseView都有自己的ViewModel,并且为了使每个ViewModel保持与ImportProcessViewModel的关系,以便IrData
的ObservableCollection强加的依赖关系。
研发
我已经对彼此通信的ViewModels进行了研究,但大多数结果都涉及使用特定MVVM框架编写的应用程序。我没有使用框架,在项目的这一点上,重构它以开始使用框架为时已晚。
但是,我确实找到了这篇文章,"hbarck"提供的答案建议了一些简单的东西,例如组合来实现我想要的结果,但由于我对DataTemplates没有太多经验,我不明白当他/她建议公开"用户控件的ViewModel作为主ViewModel上的属性,并将ContentControl绑定到此属性时是什么意思, 然后通过数据模板实例化视图(即用户控件(
具体来说,我不明白"将 ContentControl 绑定到此属性,然后通过数据模板实例化视图">是什么意思。
有人可以通过代码示例澄清在此示例上下文中通过 DataTemplate 实例化视图的含义吗?
此外,这是一个好方法(如"hbarck"所建议的那样(?
正如人们所看到的,我已经将 ContentControl 的 Content 属性设置为要实例化的阶段视图。我只是不知道涉及数据模板会是什么样子。
我不明白当他/她建议暴露"时是什么意思 用户控件的视图模型作为主视图模型上的属性,并绑定 此属性的内容控件,然后实例化 通过数据模板查看(即用户控件(">
DataTemplate
允许您指定视图(如用户控件(和视图模型之间的关系。
<DataTemplate DataType="{x:Type myApp:MyViewModel}">
<myApp:MyUserControl />
</DataTemplate>
这会告知ContentPresenter
只要其内容属性设置为 MyViewModel
的实例,就会显示MyUserControl
。视图模型将用作用户控件DataContext
。通常,DataTemplate
会添加到应用程序资源中。
该答案的作者的意思是,您可以拥有一个具有另一个视图模型类型的属性的视图模型,该属性绑定到ContentPresenter
的Content
属性。
<ContentPresenter Content="{Binding ParentViewModel.ChildViewModelProperty}"/>
如果您有一个指定ChildViewModel
与用户控件之间关系的DataTemplate
,WPF 将自动将用户控件加载到视图中。
我对另一个问题提供的这个答案也可能为您提供一些帮助。
我需要分解它,以便每个相位视图都有自己的视图模型, 但也使得每个视图模型保持一个关系回到 导入进程视图模型。
这将允许您将视图模型分解为更小、更易于管理的视图模型,这些模型可以照顾自己。这将给您留下视图模型之间通信的问题。
如果按照建议嵌套视图模型,则子视图模型可能会公开父视图模型可以绑定到的事件,以便在发生更改时收到通知。像这样:
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...
}
}
这很简单,不需要任何其他框架。有些人可能会反对这种方法,但它是一个有效的解决方案。缺点是视图模型必须知道彼此的存在才能订阅事件,因此最终可能会紧密耦合。不过,您可以使用标准的面向对象设计原则来解决这个问题(即从接口派生子视图模型,以便父级只知道接口而不是实现(。
如果你真的想进行松散耦合的通信,那么你需要使用某种事件聚合或消息总线系统。这与上述方法类似,只是有一个对象位于视图模型之间并充当中介,以便视图模型不必知道彼此的存在。我在这里的回答提供了更多信息。
有预先存在的解决方案可用,但这将涉及采用额外的框架。我建议使用Josh Smiths MVVM基础,因为它非常简单,无论如何你只需要使用一个类。
虽然本杰明的回答非常详细且非常有帮助,但我想澄清一下我在另一篇文章中写的内容如何适用于您的问题:
- 对于不同的阶段,您将有三个不同的PhaseViewModel类,可能派生自一个公共基类,例如PhaseVMBase。
- 您可能有一个 CurrentPhaseVM 属性,而不是 CurrentPhaseView 属性。这将是 Object 或 PhaseVMBase 类型,并返回三个 PhaseViewModel 类之一,具体取决于用户在主 ViewModel 中选择的内容。
- PhaseVMBase 将有一个 UpdateData 方法,每当主 ViewModel 收到应由相位视图处理的新数据时,它就会调用该方法。主 ViewModel 会在当前碰巧是 CurrentPhaseVM 的任何东西上调用此方法。PhaseViewModels 将实现 INotifyPropertyChanged,以便 UpdateData 导致的更改对绑定控件可见。
- 您的数据模板将在主视图的资源中声明,例如主窗口,
喜欢这个:
<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:键,只有数据类型值。如果这样声明,则当要求分别显示类型为 Phase1VM、Phase2VM 或 Phase3VM 的对象时,WPF 将选择适当的 DataTemplate。Phase1View,Phase2View和Phase3View将是用户控件,它们知道如何显示不同的ViewModels。他们不会自己实例化他们的 ViewModels,但希望他们的 DataContext 被设置为来自外部的各自 ViewModel 的实例。
假设应该显示阶段视图的 ContentControl 在主视图中声明,并且那里的 DataContext 将是主视图模型,您将像这样声明 ContentControl:
<ContentControl Content="{Binding CurrentPhaseVM}"/>
根据当前阶段虚拟机的实际类型,这将选择三个数据模板之一,并显示相应的用户控件。UserControl 的 DataContext 将自动成为 ContentControl 的内容,因为该对象将导致选择数据模板。
编辑:列表和代码格式似乎不在一起...