Click here to Skip to main content
11,705,610 members (49,885 online)
Click here to Skip to main content

Resetting a View Model in WPF MVVM applications without code-behind in the view

, 17 Feb 2011 CPOL 50.1K 681 15
Rate this:
Please Sign up or sign in to vote.
The core idea behind this code and article is to provide an uncomplicated approach to reset a view-model without having to adjust DataContext references in views.

Introduction

The core idea behind this code and article is to provide an uncomplicated approach to reset a view-model without having to adjust DataContext references in views. It all started with an MSDN forum thread where someone asked if there was an easy way to clear a view model. He felt that clearing every property in the view model would be too cumbersome and that it would be simpler if he could have a Cancel button that would reset the DataContext to a new view model instance. The negative (so to speak) would be that he would have to have actual code in his view (the handler for the Cancel button) that did this. This problem intrigued me and I thought it would be an interesting exercise to design a set of classes that would make this a seamless process.

I kept two fundamental requirements in mind when I designed this class :

  1. The developer should be able to reset the view model without any code in his view.
  2. The class should be transparent, so the user should not have to change his existing view model in any way to use this class.

This code will only work with WPF, and I have tested this with .NET versions 4.0 (full and client profile) as well as 3.5 (full and client profile). The demo project is for Visual Studio 2010, but since it's .NET 3.5 compatible, you can easily use this from Visual Studio 2008 too. Unfortunately the code will not work on SilverLight as the classes it uses such as TypeDescriptionProvider are not available in SilverLight (even in version 4.0).

And lest anyone writes this off as yet another over-designed MVVM class targeting those who are anal retentive about avoiding code in their view, I think this class has value outside of it's code-in-the-view avoidance capabilities. *grin*

Why use this class at all?

Basarat Ali Syed wondered why I even needed to write this class. His exact words were:

Why not just use a ViewModel locator (which you are using) combined with a Messenger (also called mediator) to pass a message from the current View to the ViewModel locator to setup new properties.

This was basically what I replied to him:

While people can do that if they want to, not everyone likes that approach for these reasons:

  1. It's almost impossible to avoid code behind, since the view has to trigger a need to update the view model.
  2. You may have to have a View reference in the View Model (which breaks basic MVVM). Even if you use a Mediator, that's still just one level of indirection back to the view.
  3. Setting up new properties is a hassle, specially for complex view model classes

What my class achieves is this:

  1. You basically re-instantiate the view model. (this is analogous to having the magical ability to use the same this-pointer in C++ to point to a completely new instance, but at the same address)
  2. The view / original view model need to know nothing about what's going on (meaning the class is easy to plug in and out)
  3. No code behind in the view
  4. No need to add mediator/in-between classes for communication

And finally, it's important to remember that this is not the sort of class that you will just use everywhere and all the time. It has a specific design purpose and it does that role well. Feel free to ask more questions on this through the forum below.

Using the class with an existing MVVM application

Consider that you have an existing MVVM application where your MainWindow view is bound to a MainViewModel instance.

<Window x:Class="ResettableViewModelDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ResettableViewModelDemo"
        Title="Resettable View Model Demo Application" Height="350" Width="329" ResizeMode="NoResize"
        DataContext="{Binding Source={x:Static local:ViewModels.MainViewModel}}" Background="#FF485093">
    <Grid Margin="5" TextBlock.Foreground="White" TextBlock.FontSize="15">
        <StackPanel Orientation="Vertical" HorizontalAlignment="Left">
            <StackPanel Orientation="Horizontal">
                <TextBlock Width="90" VerticalAlignment="Center">Item Code</TextBlock>

Notice how the DataContext is set to a static property in the ViewModels class.

class ViewModels
{
    private static object mainViewModel = new MainViewModel();

    public static object MainViewModel
    {
        get { return ViewModels.mainViewModel; }
    }
}

MainViewModel will be your existing view model implementation.

class MainViewModel : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;

  private void FirePropertyChanged(string propertyName)
  {
      PropertyChangedEventHandler handler = PropertyChanged;

      if (handler != null)
      {
          handler(this, new PropertyChangedEventArgs(propertyName));
      }
  }        

  private int itemCode = -1;

  public int ItemCode
  {
      get
      {
          return itemCode;
      }

      set
      {
          if (itemCode != value)
          {
              itemCode = value;
              FirePropertyChanged("ItemCode");
          }
      }
  }

  private string description = "unknown";

  public string Description
  {
      get
      {
          return description;
      }

      set
      {
          if (description != value)
          {
              description = value;
              FirePropertyChanged("Description");
          }
      }
  }

  private ICommand exitCommand;

  public ICommand ExitCommand
  {
      get
      {
          return exitCommand ?? (exitCommand = new DelegateCommand(() =>
          {
              Application.Current.MainWindow.Close();
          }));
      }
  }

  private ICommand addCommand;

  public ICommand AddCommand
  {
      get
      {
          return addCommand ?? (addCommand = new DelegateCommand(() =>
          {
              entries.Add(new { ItemCode = ItemCode, Description = Description });
          }));
      }
  }

  private ObservableCollection<object> entries = new ObservableCollection<object>();

  public ObservableCollection<object> Entries
  {
      get { return entries; }
  }

}

Now, assume that you have a new requirement to add a Cancel button to the view. Clicking the Cancel button must reset the two TextBoxes to the default values, and should also clear the ListBox. Without the ResettableViewModel class, you have two options - either reset all the bound properties through a Cancel command method, or add code-behind in your view to reset the DataContext to a brand new instance of the MainViewModel class. The former approach may be rather a lot of work when your view model is complex, and if you ever add/change a bound property or collection, you need to remember to reset that too. The latter approach is simple but a lot of MVVM aficionados would cringe at the thought of doing that. Oh, isn't this a conundrum? Enter the ResettableViewModel class!

Once you add the required source files to your project (or alternatively, add a reference to an assembly that contains these classes), you just need to do two little things. The first task is to change the static property to return a ResettableViewModel instance. (Note: if you set your DataContext in some other way, you'd have to make appropriate changes to do something similar)

class ViewModels
{
  private static object mainViewModel = 
      new NSViewModelExtensions.ResettableViewModel(new MainViewModel());

  public static object MainViewModel
  {
      get { return ViewModels.mainViewModel; }
  }
}

Now add a Cancel button to your XAML and bind its Command as follows.

<StackPanel Orientation="Horizontal">
  <Button Command="{Binding ResetCommand}" Width="90" Margin="5">Cancel</Button>

Notice how the Cancel button's command is bound to a ResetCommand. This is provided by the ResettableViewModel class, everything else will continue to come from your MainViewModel class. That's all, you are done! If you run the app now, clicking Cancel will reset your view model to its  initial state. Now, that wasn't a lot of work, was it?

Custom view model initialization

In some cases, your view model may not have a parameterless constructor, or you may need to set some initial properties. For those scenarios, the class provides a constructor that takes a Func<object> delegate that will be used to create the view model instance. Here's a simple example:

object mainViewModel = new NSViewModelExtensions.ResettableViewModel(
                () => new MainViewModel());

Here's a more typical example:

object mainViewModel = new NSViewModelExtensions.ResettableViewModel(
    () => 
        {
            return new MainViewModel()
            {
                ItemCode = 999,
                Description = "Default Item"
            };
        });

One caveat

If your view model has a ResetCommand property, it will will overridden by the ResetCommand property in the ResettableViewModel class. While it would probably be quite a rare situation, if you do run into it, then the workaround is to rename your property - sorry about that!

Implementation details

Put simply the ResettableViewModel class acts as a proxy to the actual view model instance. The only API newly added by the the ResettableViewModel class is the reset command, everything else comes from the underlying view model. It does this by implementing a custom TypeDescriptionProvider which dynamically provides properties on the ResettableViewModel instance that are fetched from the underlying view model object. I will quickly go through the various classes involved, and although the code is written to be quite self explanatory, I will add my comments where required. Here's what the ResettableViewModel class looks like.

[TypeDescriptionProvider(typeof(ResettableViewModelTypeDescriptionProvider))]
sealed class ResettableViewModel : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;

  private void FirePropertyChanged(string propertyName)
  {
      PropertyChangedEventHandler handler = PropertyChanged;

      if (handler != null)
      {
          handler(this, new PropertyChangedEventArgs(propertyName));
      }
  }

  private static string ErrorViewModelTypeHasToMatch = 
    "The type of the new View Model has to match that of the old View Model.";

  private Func<object> creatorMethod;

  public ResettableViewModel(object innerViewModel, Func<object> creatorMethod = null)
  {
      this.InnerViewModel = innerViewModel;
      this.creatorMethod = creatorMethod;
  }

  public ResettableViewModel(Func<object> creatorMethod)
  {
      this.InnerViewModel = (this.creatorMethod = creatorMethod)();            
  }

  public ResettableViewModel(Type innerViewModelType)
  {
      this.InnerViewModel = Activator.CreateInstance(innerViewModelType);
  }

  internal object InnerViewModel { get; private set; }

  private ICommand resetCommand;

  public ICommand ResetCommand
  {
      get
      {
          return resetCommand ?? (resetCommand = new InternalDelegateCommand(() =>
              {                        
                  if (creatorMethod == null)
                  {
                      this.InnerViewModel = Activator.CreateInstance(
                          this.InnerViewModel.GetType());
                  }
                  else
                  {
                      var newViewModel = creatorMethod();
                      
                      if (this.InnerViewModel.GetType() != newViewModel.GetType())
                      {
                          throw new InvalidOperationException(
                              ResettableViewModel.ErrorViewModelTypeHasToMatch);
                      }

                      this.InnerViewModel = newViewModel;
                  }
                  
                  FirePropertyChanged(String.Empty);
              }));
      }
  }

  class InternalDelegateCommand : ICommand
  {
      private readonly Action executeMethod;

      public InternalDelegateCommand(Action executeMethod)
      {
          this.executeMethod = executeMethod;
      }

      public void Execute(object parameter)
      {
          if (this.executeMethod != null)
          {
              this.executeMethod();
          }
      }

      public bool CanExecute(object parameter)
      {
          return true;
      }

      public event EventHandler CanExecuteChanged;
  }
}

Notice how the class has a TypeDescriptionProvider associated with it of type ResettableViewModelTypeDescriptionProvider (listed below). The reason I have a private inner class that implements ICommand is that I did not want to have any dependencies on any external command classes. This way, whatever MVVM framework you are on, the ResettableViewModel class would work smoothly since it has its own command class.

Pro-tip to update all bindings

Notice the call to FirePropertyChanged passing an empty string, well that will update every binding you have in your view.

The ResettableViewModelTypeDescriptionProvider class is simple and basically just provides a way for us to specify the custom type descriptor for our class.

class ResettableViewModelTypeDescriptionProvider : TypeDescriptionProvider
{
    private static TypeDescriptionProvider defaultTypeProvider = 
        TypeDescriptor.GetProvider(typeof(ResettableViewModel));

    public ResettableViewModelTypeDescriptionProvider()
        : base(defaultTypeProvider)
    {
    }

    public override ICustomTypeDescriptor GetTypeDescriptor(
        Type objectType, object instance)
    {
        ICustomTypeDescriptor defaultDescriptor = 
            base.GetTypeDescriptor(objectType, instance);
        return instance == null ? defaultDescriptor : 
          new ResettableViewModelCustomTypeDescriptor(defaultDescriptor, instance);
    }
}

ResettableViewModelCustomTypeDescriptor is where we associate the properties in the inner class with our proxy class (the word proxy used very loosely there of course).

class ResettableViewModelCustomTypeDescriptor : CustomTypeDescriptor
{
  public ResettableViewModelCustomTypeDescriptor(ICustomTypeDescriptor parent, object instance)
      : base(parent)
  {
      customFields.AddRange(TypeDescriptor.GetProperties(
        ((ResettableViewModel)instance).InnerViewModel)
          .Cast<PropertyDescriptor>()
          .Select(pd => new ResettableViewModelCustomField(
                pd.Name, pd.PropertyType))
          .Select(cf => new ResettableViewModelCustomFieldPropertyDescriptor(
                cf)).Cast<PropertyDescriptor>());
  }

  private List<PropertyDescriptor> customFields = new List<PropertyDescriptor>();

  public override PropertyDescriptorCollection GetProperties()
  {
      return new PropertyDescriptorCollection(base.GetProperties()
              .Cast<PropertyDescriptor>()
              .Union(customFields)
              .ToArray());
  }

  public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
  {
      return new PropertyDescriptorCollection(base.GetProperties(attributes)
              .Cast<PropertyDescriptor>()
              .Union(customFields)
              .ToArray());
  }
}

ResettableViewModelCustomField is a simple class that represents a property. It's used to store the property name and property type for all the properties in the underlying view model instance.

class ResettableViewModelCustomField
{
  public ResettableViewModelCustomField(String name, Type dataType)
  {
      Name = name;
      DataType = dataType;
  }

  public String Name { get; private set; }

  public Type DataType { get; private set; }
}

ResettableViewModelCustomFieldPropertyDescriptor is where we actually fetch the property values from the underlying instance and return it to whoever asks for these properties on the outer class instance.

class ResettableViewModelCustomFieldPropertyDescriptor : PropertyDescriptor
{
  public ResettableViewModelCustomField CustomField { get; private set; }

  public ResettableViewModelCustomFieldPropertyDescriptor(
    ResettableViewModelCustomField customField)
      : base(customField.Name, new Attribute[0])
  {
      CustomField = customField;
  }

  public override bool CanResetValue(object component)
  {
      return true;
  }

  public override Type ComponentType
  {
      get 
      {
          return typeof(ResettableViewModel);
      }
  }

  public override object GetValue(object component)
  {
      return GetInnerPropertyInfo(component).GetValue(
        ((ResettableViewModel)component).InnerViewModel, new object[0]);
  }

  public override bool IsReadOnly
  {
      get 
      {
          return false;
      }
  }

  public override Type PropertyType
  {
      get
      {
          return CustomField.DataType;
      }
  }

  public override void ResetValue(object component)
  {
      this.SetValue(component, Activator.CreateInstance(CustomField.DataType));
  }

  public override void SetValue(object component, object value)
  {
      GetInnerPropertyInfo(component).SetValue(
        ((ResettableViewModel)component).InnerViewModel, value, new object[0]);
  }

  public override bool ShouldSerializeValue(object component)
  {
      return false;
  }

  private PropertyInfo propertyInfo;

  private PropertyInfo GetInnerPropertyInfo(object component)
  {
      return propertyInfo ?? (propertyInfo = ((ResettableViewModel)component)
        .InnerViewModel.GetType().GetProperty(this.CustomField.Name));
  }
}

The highlighted code in the above snippet is where we get and set properties on the underlying view model instance.

Conclusion

That's it, I guess. As usual, please feel free to give me feedback and criticism through the article forum. Your votes of 5 will be deeply appreciated too, and I will have a shot of single malt scotch for every 5 I get, and then maybe a few more!

History

  • February 16, 2011 - Article first published.
  • February 17, 2011 - Added text better explaining why this class was designed.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Nish Nishant
United States United States
Nish Nishant is a Software Architect/Consultant based out of Columbus, Ohio. He has over 15 years of software industry experience in various roles including Lead Software Architect, Principal Software Engineer, and Product Manager. Nish is a recipient of the annual Microsoft Visual C++ MVP Award since 2002 (13 consecutive awards as of 2014).

Nish is an industry acknowledged expert in the Microsoft technology stack. He authored
C++/CLI in Action for Manning Publications in 2005, and had previously co-authored
Extending MFC Applications with the .NET Framework for Addison Wesley in 2003. In addition, he has over 140 published technology articles on CodeProject.com and another 250+ blog articles on his
WordPress blog. Nish is vastly experienced in team management, mentoring teams, and directing all stages of software development.

Contact Nish : You can reach Nish on his google email id voidnish.

Website and Blog

You may also be interested in...

Comments and Discussions

 
QuestionHow Pin
RainClick1-Feb-12 6:33
memberRainClick1-Feb-12 6:33 
QuestionResetting all ViewModels? Pin
Member 459998115-Jan-12 16:13
memberMember 459998115-Jan-12 16:13 
GeneralMy vote of 5 Pin
Jay R. Wren22-Feb-11 10:22
memberJay R. Wren22-Feb-11 10:22 
GeneralRe: My vote of 5 Pin
Nishant Sivakumar22-Feb-11 10:35
mvpNishant Sivakumar22-Feb-11 10:35 
Generalanother approach Pin
John Adams19-Feb-11 14:33
memberJohn Adams19-Feb-11 14:33 
AnswerRe: another approach Pin
Nishant Sivakumar19-Feb-11 14:36
mvpNishant Sivakumar19-Feb-11 14:36 
Pretty good idea that. Take a 5!

GeneralMy vote of 5 Pin
Kunal_Chowdhury18-Feb-11 6:55
mvpKunal_Chowdhury18-Feb-11 6:55 
GeneralRe: My vote of 5 Pin
Nishant Sivakumar18-Feb-11 6:55
mvpNishant Sivakumar18-Feb-11 6:55 
GeneralOne weird requirement Pin
Sacha Barber17-Feb-11 20:18
mvpSacha Barber17-Feb-11 20:18 
GeneralRe: One weird requirement Pin
Nishant Sivakumar18-Feb-11 6:22
mvpNishant Sivakumar18-Feb-11 6:22 
GeneralMy vote of 5 Pin
Basarat Ali Syed17-Feb-11 18:09
memberBasarat Ali Syed17-Feb-11 18:09 
GeneralRe: My vote of 5 Pin
Nishant Sivakumar18-Feb-11 6:23
mvpNishant Sivakumar18-Feb-11 6:23 
QuestionIsn't there a much simpler way of doing this? Pin
neil.hall17-Feb-11 12:27
memberneil.hall17-Feb-11 12:27 
AnswerRe: Isn't there a much simpler way of doing this? Pin
Nishant Sivakumar17-Feb-11 13:18
mvpNishant Sivakumar17-Feb-11 13:18 
GeneralRe: Isn't there a much simpler way of doing this? Pin
neil.hall17-Feb-11 13:58
memberneil.hall17-Feb-11 13:58 
GeneralRe: Isn't there a much simpler way of doing this? Pin
Nishant Sivakumar17-Feb-11 14:16
mvpNishant Sivakumar17-Feb-11 14:16 
GeneralMy vote of 4 Pin
Basarat Ali Syed16-Feb-11 18:40
memberBasarat Ali Syed16-Feb-11 18:40 
AnswerRe: My vote of 4 Pin
Nishant Sivakumar17-Feb-11 1:48
mvpNishant Sivakumar17-Feb-11 1:48 
GeneralRe: My vote of 4 Pin
Basarat Ali Syed17-Feb-11 18:13
memberBasarat Ali Syed17-Feb-11 18:13 
AnswerRe: My vote of 4 Pin
Nishant Sivakumar18-Feb-11 6:25
mvpNishant Sivakumar18-Feb-11 6:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150819.1 | Last Updated 17 Feb 2011
Article Copyright 2011 by Nish Nishant
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid