Click here to Skip to main content
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
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>
                <TextBox Text="{Binding ItemCode}" Width="197" Margin="8" />
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Width="90" VerticalAlignment="Center">Description</TextBlock>
                <TextBox Text="{Binding Description}" Width="196" Margin="8" />
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Button Command="{Binding AddCommand}" Width="90" Margin="5">Add</Button>
                <Button Command="{Binding ExitCommand}" Width="90" Margin="5">Exit</Button>
            </StackPanel>
            <ListBox ItemsSource="{Binding Entries}" Height="180" Width="293" />
        </StackPanel>
    </Grid>
</Window>

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>
  <Button Command="{Binding AddCommand}" Width="90" Margin="5">Add</Button>
  <Button Command="{Binding ExitCommand}" Width="90" Margin="5">Exit</Button>
</StackPanel>

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 Sivakumar

United States United States
Nish is a real nice guy who has been writing code since 1990 when he first got his hands on an 8088 with 640 KB RAM. Originally from sunny Trivandrum in India, he has been living in various places over the past few years and often thinks it’s time he settled down somewhere.
 
Nish has been a Microsoft Visual C++ MVP since October, 2002 - awfully nice of Microsoft, he thinks. He maintains an MVP tips and tricks web site - www.voidnish.com where you can find a consolidated list of his articles, writings and ideas on VC++, MFC, .NET and C++/CLI. Oh, and you might want to check out his blog on C++/CLI, MFC, .NET and a lot of other stuff - blog.voidnish.com.
 
Nish loves reading Science Fiction, P G Wodehouse and Agatha Christie, and also fancies himself to be a decent writer of sorts. He has authored a romantic comedy Summer Love and Some more Cricket as well as a programming book – Extending MFC applications with the .NET Framework.
 
Nish's latest book C++/CLI in Action published by Manning Publications is now available for purchase. You can read more about the book on his blog.
 
Despite his wife's attempts to get him into cooking, his best effort so far has been a badly done omelette. Some day, he hopes to be a good cook, and to cook a tasty dinner for his wife.

Comments and Discussions

 
QuestionHow PinmemberRainClick1-Feb-12 6:33 
QuestionResetting all ViewModels? PinmemberMember 459998115-Jan-12 16:13 
GeneralMy vote of 5 PinmemberJay R. Wren22-Feb-11 10:22 
GeneralRe: My vote of 5 PinmvpNishant Sivakumar22-Feb-11 10:35 
Generalanother approach PinmemberJohn Adams19-Feb-11 14:33 
AnswerRe: another approach PinmvpNishant Sivakumar19-Feb-11 14:36 
GeneralMy vote of 5 PinmvpKunal_Chowdhury18-Feb-11 6:55 
GeneralRe: My vote of 5 PinmvpNishant Sivakumar18-Feb-11 6:55 
GeneralOne weird requirement PinmvpSacha Barber17-Feb-11 20:18 
GeneralRe: One weird requirement PinmvpNishant Sivakumar18-Feb-11 6:22 
GeneralMy vote of 5 PinmemberBasarat Ali Syed17-Feb-11 18:09 
GeneralRe: My vote of 5 PinmvpNishant Sivakumar18-Feb-11 6:23 
QuestionIsn't there a much simpler way of doing this? Pinmemberneil.hall17-Feb-11 12:27 
AnswerRe: Isn't there a much simpler way of doing this? PinmvpNishant Sivakumar17-Feb-11 13:18 
GeneralRe: Isn't there a much simpler way of doing this? Pinmemberneil.hall17-Feb-11 13:58 
GeneralRe: Isn't there a much simpler way of doing this? PinmvpNishant Sivakumar17-Feb-11 14:16 
GeneralMy vote of 4 PinmemberBasarat Ali Syed16-Feb-11 18:40 
AnswerRe: My vote of 4 PinmvpNishant Sivakumar17-Feb-11 1:48 
GeneralRe: My vote of 4 PinmemberBasarat Ali Syed17-Feb-11 18:13 
AnswerRe: My vote of 4 PinmvpNishant 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 | Mobile
Web02 | 2.8.141015.1 | Last Updated 17 Feb 2011
Article Copyright 2011 by Nish Sivakumar
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid