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

时间:2022-04-22 11:58:41

Introduction

I have an application that imports lab instrument data while it is running. This data is imported and then displayed in a ListView at an interval set by the end-user as per his or her testing requirements. When a value of interest appears in this ListView that they watch, they then press a Start button and the application begins performing calculations on that datum and subsequent data until a Stop button is pressed. So on the left side of the screen is a View for displaying the imported data and on the right side is another View for watching the values and statistics as they are calculated and displayed.

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

The Current Code

现行守则

The View that displays the ListView where data is imported to is the ImportProcessView.xaml and it sets its DataContext to the ImportProcessViewModel.cs. The VM I've just introduced has a property ObservableCollection<IrData> that the ListView, I've also just described, binds to. Now to the interesting part...

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

The ImportProcessView has a ContentControl that sets it's content dynamically a UserControl representing the controls and fields specific to the type of Phase that is chosen by the end-user.

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

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

There are three PhaseViews, each in its own User Control and each sets it's DataContext to the ImportProcessViewModel. As a result I am getting some severe VM bloat to the tune of 2000 lines. Ridiculous. I know. The reason for the bloat is because the ImporProcessViewModel is maintaining state through properties for each of the three PhaseViews and not only that but contains methods for performing calculations whose data is stored and displayed in these "PhaseViews".

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

What I am trying to achieve

我想要实现的目标

Obviously before the ImportProcessViewModel becomes more unwieldy, I need to break it up so that each PhaseView has its own ViewModel, but also such that each ViewModel maintains a relationship back to the ImportProcessViewModel for sake of the dependency imposed by the ObservableCollection of IrData.

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

R&D

I've done my research on ViewModels communicating with each other, but most of the results involve applications that were written with a specific MVVM framework. I am not using a framework, and at this point in the project it would be too late to refactor it to start using one.

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

I did, however, find this article and the answer offered by 'hbarck' suggests something simple like composition to achieve the result I want, but since I don't have much experience with DataTemplates I don't understand what is meant when he/she suggests exposing "the UserControl's ViewModel as a property on the main ViewModel, and bind a ContentControl to this property, which would then instantiate the View (i.e. the UserControl) through a DataTemplate"

然而,我确实找到了这篇文章,'hbarck'提供的答案提出了一些简单的构图来实现我想要的结果,但由于我对DataTemplates没有太多经验,所以我不明白他/他的意思是什么她建议将“UserControl的ViewModel”暴露为主ViewModel上的属性,并将ContentControl绑定到此属性,然后通过DataTemplate实例化View(即UserControl)“

Specifically, I don't understand what is meant by "bind a ContentControl to this property which would then instantiate the View through a DataTemplate".

具体来说,我不明白“将ContentControl绑定到此属性然后通过DataTemplate实例化View”的含义。

Can someone clarify by way of an code example what is meant by instantiating a view through a DataTemplate in the context of this example?

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

Additionally, is this a good approach (as suggested by 'hbarck')?

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

As one can see, I am already setting the Content property of a ContentControl to the Phase View that is to be instantiated. I just don't know know what involving a DataTemplate would look like.

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

2 个解决方案

#1


4  

I don't understand what is meant when he/she suggests exposing "the UserControl's ViewModel as a property on the main ViewModel, and bind a ContentControl to this property, which would then instantiate the View (i.e. the UserControl) through a DataTemplate"

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

A DataTemplate allows you to specify a relationship between a view (such as a user control) and a view model.

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

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

This tells a ContentPresenter to display MyUserControl whenever its content property is set to an instance of MyViewModel. The view model will be used as the user controls DataContext. Typically, the DataTemplate is added to your application resources.

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

What the author of that answer is saying is that you could have a viewModel that has a property of another viewModel type which is bound to the Content property of the ContentPresenter.

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

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

Providing you have a DataTemplate that specifies a relationship between your ChildViewModel and your user control, WPF will automatically load the user control into your view.

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

This answer I provided to another question might also provide you with some help.

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

I need to break it up so that each PhaseView has its own ViewModel, but also such that each ViewModel maintains a relationship back to the ImportProcessViewModel.

我需要将其分解,以便每个PhaseView都有自己的ViewModel,但每个ViewModel都会维护一个返回ImportProcessViewModel的关系。

This will allow you to break your viewModels into smaller, more manageable viewModels that look after themselves. This will leave you with the problem of communicating between the viewModels.

这将允许您将viewModel分解为更小,更易于管理的viewModel,它们可以自我管理。这将使您在viewModel之间进行通信时遇到问题。

If you nest your viewModels as suggested, then your child viewModels could expose events that the parent viewModel can bind to so it is notified when something changes. Something like this:

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

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...
    }
}

This is simple and doesn't require any additional frameworks. Some might argue against this method but it is a valid solution that works. The downside is that the viewModels have to know about each others existence in order to subscribe to the events so can end up being tightly-coupled. You can use standard object-oriented design principles to get around this though (I.E. derive your child viewModel from an interface so that the parent only knows about the interface and not the implementation).

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

If you really want to go for loosely-coupled communication then you need to use some sort of event aggregation or message bus system. This is similar to the above method except there is an object that sits between the view models and acts as a mediator so that the viewModels do not have to know of each others existence. My answer here provides some more information.

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

There are pre-existing solutions available but this would involve taking on an additional framework. I would advise using Josh Smiths MVVM foundation as it is very simple and you would only need to use a single class anyway.

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

#2


3  

While Benjamin's answer is really elaborate and very helpful, I'd like to clarify how what I wrote in the other post would apply to your problem:

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

  • You'd have three different PhaseViewModel-Classes for your different phases, probably derived from one common base class, let's say PhaseVMBase.
  • 你有不同阶段的三个不同的PhaseViewModel-Classes,可能来自一个公共基类,比如PhaseVMBase。

  • Instead of a CurrentPhaseView property, you'd probably have a CurrentPhaseVM property. This would be of type Object or PhaseVMBase, and return one of the three PhaseViewModel classes, depending on what the user chose in the main ViewModel.
  • 您可能拥有CurrentPhaseVM属性而不是CurrentPhaseView属性。这将是Object或PhaseVMBase类型,并返回三个PhaseViewModel类中的一个,具体取决于用户在主ViewModel中选择的内容。

  • PhaseVMBase would have an UpdateData method, which would be called by the main ViewModel whenever it received new data that should be processed by the phase view. The main ViewModel would call this method on whatever happened to be the CurrentPhaseVM at the moment. The PhaseViewModels would implement INotifyPropertyChanged, so that changes as a result of UpdateData would be visible to bound controls.
  • PhaseVMBase将具有UpdateData方法,只要收到应由阶段视图处理的新数据,主ViewModel就会调用该方法。主要的ViewModel会在当前发生在CurrentPhaseVM上的情况下调用此方法。 PhaseViewModels将实现INotifyPropertyChanged,以便绑定控件可以看到UpdateData的更改。

  • Your DataTemplates would be declared in the resources of the main view, e.g. the main window,
  • 您的DataTemplates将在主视图的资源中声明,例如主窗口,

like this:

<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>

Notice that there is no x:Key, only the DataType value. If declared like this, WPF would choose the appropriate DataTemplate when asked to display an object of type Phase1VM, Phase2VM or Phase3VM, respectively. Phase1View, Phase2View and Phase3View would be UserControls which would know how to display the different ViewModels. They wouldn't instantiate their ViewModels themselves, but expect that their DataContext is set to an instance of their respective ViewModel from outside.

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

Under the assumption that the ContentControl which should show the phase view is declared in the main view, and that the DataContext there would be the main ViewModel, you'd declare the ContentControl like this:

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

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

Depending on the actual type of CurrentPhaseVM, this will choose one of the three DataTemplates, and display the appropriate UserControl. The UserControl's DataContext would automatically be the ContentControl's Content, since that would the object which caused the DataTemplate to be chosen.

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

EDIT: Lists and code formatting don't go together, it seems...

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

#1


4  

I don't understand what is meant when he/she suggests exposing "the UserControl's ViewModel as a property on the main ViewModel, and bind a ContentControl to this property, which would then instantiate the View (i.e. the UserControl) through a DataTemplate"

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

A DataTemplate allows you to specify a relationship between a view (such as a user control) and a view model.

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

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

This tells a ContentPresenter to display MyUserControl whenever its content property is set to an instance of MyViewModel. The view model will be used as the user controls DataContext. Typically, the DataTemplate is added to your application resources.

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

What the author of that answer is saying is that you could have a viewModel that has a property of another viewModel type which is bound to the Content property of the ContentPresenter.

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

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

Providing you have a DataTemplate that specifies a relationship between your ChildViewModel and your user control, WPF will automatically load the user control into your view.

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

This answer I provided to another question might also provide you with some help.

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

I need to break it up so that each PhaseView has its own ViewModel, but also such that each ViewModel maintains a relationship back to the ImportProcessViewModel.

我需要将其分解,以便每个PhaseView都有自己的ViewModel,但每个ViewModel都会维护一个返回ImportProcessViewModel的关系。

This will allow you to break your viewModels into smaller, more manageable viewModels that look after themselves. This will leave you with the problem of communicating between the viewModels.

这将允许您将viewModel分解为更小,更易于管理的viewModel,它们可以自我管理。这将使您在viewModel之间进行通信时遇到问题。

If you nest your viewModels as suggested, then your child viewModels could expose events that the parent viewModel can bind to so it is notified when something changes. Something like this:

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

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...
    }
}

This is simple and doesn't require any additional frameworks. Some might argue against this method but it is a valid solution that works. The downside is that the viewModels have to know about each others existence in order to subscribe to the events so can end up being tightly-coupled. You can use standard object-oriented design principles to get around this though (I.E. derive your child viewModel from an interface so that the parent only knows about the interface and not the implementation).

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

If you really want to go for loosely-coupled communication then you need to use some sort of event aggregation or message bus system. This is similar to the above method except there is an object that sits between the view models and acts as a mediator so that the viewModels do not have to know of each others existence. My answer here provides some more information.

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

There are pre-existing solutions available but this would involve taking on an additional framework. I would advise using Josh Smiths MVVM foundation as it is very simple and you would only need to use a single class anyway.

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

#2


3  

While Benjamin's answer is really elaborate and very helpful, I'd like to clarify how what I wrote in the other post would apply to your problem:

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

  • You'd have three different PhaseViewModel-Classes for your different phases, probably derived from one common base class, let's say PhaseVMBase.
  • 你有不同阶段的三个不同的PhaseViewModel-Classes,可能来自一个公共基类,比如PhaseVMBase。

  • Instead of a CurrentPhaseView property, you'd probably have a CurrentPhaseVM property. This would be of type Object or PhaseVMBase, and return one of the three PhaseViewModel classes, depending on what the user chose in the main ViewModel.
  • 您可能拥有CurrentPhaseVM属性而不是CurrentPhaseView属性。这将是Object或PhaseVMBase类型,并返回三个PhaseViewModel类中的一个,具体取决于用户在主ViewModel中选择的内容。

  • PhaseVMBase would have an UpdateData method, which would be called by the main ViewModel whenever it received new data that should be processed by the phase view. The main ViewModel would call this method on whatever happened to be the CurrentPhaseVM at the moment. The PhaseViewModels would implement INotifyPropertyChanged, so that changes as a result of UpdateData would be visible to bound controls.
  • PhaseVMBase将具有UpdateData方法,只要收到应由阶段视图处理的新数据,主ViewModel就会调用该方法。主要的ViewModel会在当前发生在CurrentPhaseVM上的情况下调用此方法。 PhaseViewModels将实现INotifyPropertyChanged,以便绑定控件可以看到UpdateData的更改。

  • Your DataTemplates would be declared in the resources of the main view, e.g. the main window,
  • 您的DataTemplates将在主视图的资源中声明,例如主窗口,

like this:

<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>

Notice that there is no x:Key, only the DataType value. If declared like this, WPF would choose the appropriate DataTemplate when asked to display an object of type Phase1VM, Phase2VM or Phase3VM, respectively. Phase1View, Phase2View and Phase3View would be UserControls which would know how to display the different ViewModels. They wouldn't instantiate their ViewModels themselves, but expect that their DataContext is set to an instance of their respective ViewModel from outside.

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

Under the assumption that the ContentControl which should show the phase view is declared in the main view, and that the DataContext there would be the main ViewModel, you'd declare the ContentControl like this:

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

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

Depending on the actual type of CurrentPhaseVM, this will choose one of the three DataTemplates, and display the appropriate UserControl. The UserControl's DataContext would automatically be the ContentControl's Content, since that would the object which caused the DataTemplate to be chosen.

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

EDIT: Lists and code formatting don't go together, it seems...

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