Click here to Skip to main content
15,867,686 members
Articles / Desktop Programming / WPF

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

Rate me:
Please Sign up or sign in to vote.
4.83/5 (11 votes)
17 Feb 2011CPOL7 min read 111.2K   1K   17   20
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.

Image 1

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.

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

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

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

MainViewModel will be your existing view model implementation.

C#
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)

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

C#
<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:

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

Here's a more typical example:

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

C#
[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.

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

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

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

C#
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)


Written By
United States United States
Nish Nishant is a technology enthusiast from Columbus, Ohio. He has over 20 years of software industry experience in various roles including Chief Technology Officer, Senior Solution Architect, Lead Software Architect, Principal Software Engineer, and Engineering/Architecture Team Leader. Nish is a 14-time recipient of the Microsoft Visual C++ MVP Award.

Nish authored C++/CLI in Action for Manning Publications in 2005, and 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 experienced in technology leadership, solution architecture, software architecture, cloud development (AWS and Azure), REST services, software engineering best practices, CI/CD, mentoring, and directing all stages of software development.

Nish's Technology Blog : voidnish.wordpress.com

Comments and Discussions

 
QuestionHow Pin
RainClick1-Feb-12 6:33
RainClick1-Feb-12 6:33 
QuestionResetting all ViewModels? Pin
Member 459998115-Jan-12 16:13
Member 459998115-Jan-12 16:13 
GeneralMy vote of 5 Pin
Jay R. Wren22-Feb-11 10:22
Jay R. Wren22-Feb-11 10:22 
GeneralRe: My vote of 5 Pin
Nish Nishant22-Feb-11 10:35
sitebuilderNish Nishant22-Feb-11 10:35 
Generalanother approach Pin
John Adams19-Feb-11 14:33
John Adams19-Feb-11 14:33 
AnswerRe: another approach Pin
Nish Nishant19-Feb-11 14:36
sitebuilderNish Nishant19-Feb-11 14:36 
GeneralMy vote of 5 Pin
Kunal Chowdhury «IN»18-Feb-11 6:55
professionalKunal Chowdhury «IN»18-Feb-11 6:55 
GeneralRe: My vote of 5 Pin
Nish Nishant18-Feb-11 6:55
sitebuilderNish Nishant18-Feb-11 6:55 
Thank you, Kunal.

GeneralOne weird requirement Pin
Sacha Barber17-Feb-11 20:18
Sacha Barber17-Feb-11 20:18 
GeneralRe: One weird requirement Pin
Nish Nishant18-Feb-11 6:22
sitebuilderNish Nishant18-Feb-11 6:22 
GeneralMy vote of 5 Pin
Basarat Ali Syed17-Feb-11 18:09
Basarat Ali Syed17-Feb-11 18:09 
GeneralRe: My vote of 5 Pin
Nish Nishant18-Feb-11 6:23
sitebuilderNish Nishant18-Feb-11 6:23 
QuestionIsn't there a much simpler way of doing this? Pin
NeverJustHere17-Feb-11 12:27
NeverJustHere17-Feb-11 12:27 
AnswerRe: Isn't there a much simpler way of doing this? Pin
Nish Nishant17-Feb-11 13:18
sitebuilderNish Nishant17-Feb-11 13:18 
GeneralRe: Isn't there a much simpler way of doing this? Pin
NeverJustHere17-Feb-11 13:58
NeverJustHere17-Feb-11 13:58 
GeneralRe: Isn't there a much simpler way of doing this? Pin
Nish Nishant17-Feb-11 14:16
sitebuilderNish Nishant17-Feb-11 14:16 
GeneralMy vote of 4 Pin
Basarat Ali Syed16-Feb-11 18:40
Basarat Ali Syed16-Feb-11 18:40 
AnswerRe: My vote of 4 Pin
Nish Nishant17-Feb-11 1:48
sitebuilderNish Nishant17-Feb-11 1:48 
GeneralRe: My vote of 4 Pin
Basarat Ali Syed17-Feb-11 18:13
Basarat Ali Syed17-Feb-11 18:13 
AnswerRe: My vote of 4 Pin
Nish Nishant18-Feb-11 6:25
sitebuilderNish Nishant18-Feb-11 6:25 

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

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