12
votes

I'm using MVVM in a WPF app. I'm very new to both. Let me state that I am not a purist in the MVVM pattern, I am trying to use as many best practices as I can but am trying to make what I think are reasonable compromises to make it work in our environment. For example, I am not trying to achieve 0% code in my View code-behind.

I have a couple of questions about best practices.

1) I understand I don't want my VM to know about the attached View, but is it reasonable for the View to have a reference to its VM?

2) If a control in a View opens another View (such as a dialog) should I handle this in the View? It seems wrong to handle it in the VM since then the VM has some knowledge of a specific View.

5
A View references a VM but it should not be other way. stackoverflow.com/a/3670669/1977871VivekDev

5 Answers

14
votes

1) The View has definitely a reference to the ViewModel through the DataContext. And you are allowed to cast the DataContext in your View:

public class ShellView : Window 
{
   …
   public ShellViewModel { get { return DataContext as ShellViewModel; } }

This isn’t a violation with the Model-View-ViewModel pattern.

.

2) You are right. A ViewModel shouldn’t open another View. A better approach is to use Controllers. They are responsible for the Workflow of an application.

If you are interested in more detailed information then you might have a look at the WPF Application Framework (WAF).

3
votes

1) Here are two simple practices for View's "knowing about" a ViewModel. It's reasonable for a View to know about a ViewModel (for Data Binding) -- but you may not need it in your case. See if either of these approaches help solve your problem. There are other ways, but these should be simple enough:

public View(ViewModel vm)
{
     View.DataContext = vm;
}

public Bootstrapper(View v, ViewModel vm)
{
     v.DataContext = vm;
     //or, if you want it to have no parameters
     View v = new View();
     ViewModel vm = new ViewModel();
     v.DataContext = vm;
}

The first option isn't bad if you have a service location tool, but there is a flavor of MVVM that doesn't like any code in the View's Code-Behind. The second option isn't bad either, should be simple enough for your task.

2.) This question can be a bit of a sticky point in MVVM design. If we are talking about a general Win32 MessageBox, I will often separate that logic into an additional object and put it in the VM. This way tends to a little more clear. (For example, I have selected an item in a ListBox, I have attached a Delete ICommand to that action, and in my ViewModel when this ICommand is Executed, I will poke my MessageBoxObject to ask if the user "wants to really delete" this item). More advanced "Dialogs" would use additional ViewModels and DataTemplates for those ViewModels. I prefer the Mediator approach.

2
votes

1). The view will need a reference to the view model at some level, since the viewmodel will act as the view's datacontext.

2) One way to handle this is to have a generalized viewmodel representing a dialog, that is owned by the main viewmodel (the one being used as the views datacontext.)

You can use a command to crate a new instance of a dialog viewmodel, which will have a corresponding datatemplate defined in your resources. This template will be set to bind to the dialogviewmodel type.

2
votes

Quite late, but I think this is tricky enough to deserve lots of different perspectives.


I understand I don't want my VM to know about the attached View, but is it reasonable for the View to have a reference to its VM?

As already answered, a proper View-ViewModel arrangement involves the ViewModel being assigned as the View's DataContext property. That allows DataBindings to be "automagically" established from declarative XAML, or fine-tuned via code behind.

Sometimes, you'll be tempted to write, in your code behind, something like this:

var dc = DataContext as CleverViewModel;
CleverViewModel.CleverProperty.Add(someValue); // just a simple example

I believe the proper way to achieve this sort of things is NOT to cast DataContext, but instead:

  1. Have some dedicated control in View, for example an ItemsControl with its ItemsSource two-way databound to some property in viewmodel:

    <ItemsSource x:Name="cleverControl" Visibility="Collapsed" ItemsSource="{Binding CleverProperty, Mode=TwoWay}"/>

  2. Cast the bound property instead of the whole ViewModel, in code behind:

    var collection = (ObservableCollection<double>)cleverControl.ItemsSource; collection.Add(someValue);

Note the important difference: the second approach in this example doesn't require the View to know the ViewModel type, it only needs a property named CleverProperty of type ObservableCollection<double>. This allows me to have polymorphic or even duck-typed ViewModels.


If a control in a View opens another View (such as a dialog) should I handle this in the View? It seems wrong to handle it in the VM since then the VM has some knowledge of a specific View.

This shouldn't happen in strict MVVM, and its not difficult to avoid using DataTemplates. DataTemplates map a given type of DataContext to a given type of view, so anytime the datacontext of a ContentControl changes, its display also changes, provided that you have a DataTemplate for that type:

  1. A control in the view could send a command to the ViewModel, which in turn would update some of its own properties, that would be reflected by the view.

  2. A View could contain another View, outside the knowledge of the ViewModel. In this case, the code behind can manipulate the datacontext of the contained view.

There are more subtleties, but I have been using this approach with good results. Hope this helps someone.

1
votes

Build Your Own MVVM Framework

I found the approach suggested by Rob Eisenberg very interesting.

Key points:

  1. Convention over configuration
  2. ViewModel first

Which is very similar to ASP.NET MVC philosophy.

I highly recommend watching the video.