Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ViewModels that talk to each other without a Framework

Tags:

c#

mvvm

wpf

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.

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

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.

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

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.

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.

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"

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

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?

Additionally, is this a good approach (as suggested by '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.

like image 914
Isaiah Nelson Avatar asked May 06 '13 17:05

Isaiah Nelson


2 Answers

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.
  • 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.
  • 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.
  • Your DataTemplates would be declared in the resources of the main view, e.g. the main window,

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.

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

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

like image 38
hbarck Avatar answered Nov 05 '22 11:11

hbarck


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"

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

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

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.

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

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.

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.

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:

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

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.

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.

like image 111
Benjamin Gale Avatar answered Nov 05 '22 11:11

Benjamin Gale