Friday, February 26, 2010

Streamlining property notifications in MVVM

Anyone doing Silverlight or WPF will know that the MVVM pattern is all the rage at the moment. So lots of people out there are now writing ViewModels to bind to their Views, which means they will be writing this sort of thing:
public string MyProperty
{
get { return _myProperty; }
set
{
bool changed = value != _myProperty;
if (changed)
{
_myProperty = value;
OnPropertyChanged("MyProperty");
}
}
}


When you have a ViewModel that has anything more than just a few properties, you end up having a LOT of code that while not an exact duplicate, it is doing the exact same thing to different arguments, which may or may not be of the same type. Then you multiply this problem by the number of Views you have, and the process of writing a ViewModel becomes very tedious indeed.

When you have the same code duplicated, you look to see if you can refactor it into a function, and the lines of duplicated code become a call to that new function. And when you are doing the exact same thing to different types, you look to see if you can employ generics.

To solve this issue i came up with the following:
protected void SetProperty(ref T newValue, ref T currentValue, bool notify, string propertyName, params string[] additionalProperties)
{
bool changed = notify && ((newValue != null && !newValue.Equals(currentValue)) || (newValue == null && currentValue != null));
currentValue = newValue;
if (changed)
{
OnPropertyChanged(propertyName);
if (additionalProperties != null)
foreach (string additionalProperty in additionalProperties)
OnPropertyChanged(additionalProperty);
}
}


It's not rocket science, and others have probably come up with the same kind of function, but i love it because it now saves me so much time and eliminates so much repetition. I include this function in to a base class that all my ViewModels inherit from, here it is with a sample on how to use it:
public class ViewModelBase : INotifyPropertyChanged
{

protected void SetProperty(ref T newValue, ref T currentValue, bool notify, string propertyName, params string[] additionalProperties)
{
bool changed = notify && ((newValue != null && !newValue.Equals(currentValue)) || (newValue == null && currentValue != null));
currentValue = newValue;
if (changed)
{
OnPropertyChanged(propertyName);

if (additionalProperties != null)
foreach (string additionalProperty in additionalProperties)
OnPropertyChanged(additionalProperty);
}
}

protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

public event PropertyChangedEventHandler PropertyChanged;
}

public class MyRealViewModel : ViewModelBase
{
public int NumberOfItems
{
get { return _numItems; }
set { SetProperty(ref value, ref _numItems, true, "NumberOfItems"); }
}

public bool SomeKindOfFlag
{
get { return _flag; }
set { SetProperty(ref value, ref _flag, false, ""); }
}

public LightSabre WeaponOfChoice
{
get { return _weapon; }
set { SetProperty(ref value, ref _weapon, true, "WeaponOfChoice", "SomeKindOfFlag", "NumberOfItems"); }
}

private bool _flag;
private int _numItems;
private LightSabre _weapon;
}

public class LightSabre
{
public string LightSabreName { get; set; }

public override bool Equals(object obj)
{
if (obj != null && obj as LightSabre != null)
return ((LightSabre)obj).LightSabreName == this.LightSabreName;

return false;
}
}