January 2015

You are browsing the site archives for January 2015.

Trying to be MVVM compliant is not always easy.

Take the following scenario:

There is a model with a property “Status”. This property changes values according to some logic in the model. This property should now be presented in the view. To have correct MVVM, the ViewModel has also a property “Status”, which is bound to the view.
So, now how can the model inform the viewmodel when the “Status” of the model changed?

The best bet is to have a StatusChanged event on the model and let the viewmodel subscribe to it and fire the viewmodel’s own propertychanged for it’s “Status” property. This can get quite tedious so I created a small helper: PropertyChangedProxy

For this, the model and the viewmodel need to implement INotifyPropertyChanged. Then on the viewmodel, you can create new a new instance of the PropertyChangedProxy for each property of the model where you want the viewmodel to be notified, in a typesafe way.

Such an instance would look like this:

var statusPropertyChangedProxy = new PropertyChangedProxy<MainModel, StatusType>(
	_model, m => m.Status,
	newValue => OnPropertyChanged("Status"));

So as you see, you just need to give the source (which is the model), the property of the source (as a nice expression) and an action, that should be called when the source’s property changes (which in this case is just to fire a OnPropertyChanged on the ViewModel itself).

Here’s a complete small example of a model and viewmodel, where the viewmodel is automatically notified when the model’s “Status” changes so that the viewmodel can forward this to the view:

public class MyModel : INotifyPropertyChanged
{
	private string _status;
	public string Status
	{
		get { return _status; }
		set { _status = value; OnPropertyChanged(); }
	}

	// Default INotifyPropertyChanged
	public event PropertyChangedEventHandler PropertyChanged;
	protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
	{
		var handler = PropertyChanged;
		if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
	}
}

public class MyViewModel : INotifyPropertyChanged
{
	public string Status
	{
		get { return _model.Status; }
	}

	private PropertyChangedProxy<MyModel, string> _statusPropertyChangedProxy;
	private MyModel _model;
	public MyViewModel(MyModel model)
	{
		_model = model;
		_statusPropertyChangedProxy = new PropertyChangedProxy<MyModel, string>(
			_model, myModel => myModel.Status, s => OnPropertyChanged("Status")
		);
	}

	// Default INotifyPropertyChanged
	public event PropertyChangedEventHandler PropertyChanged;
	protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
	{
		var handler = PropertyChanged;
		if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
	}
}

Here’s the source of the PropertyChangedProxy:

/// <summary>
/// Proxy class to easily take actions when a specific property in the "source" changed
/// </summary>
/// Last updated: 20.01.2015
/// <typeparam name="TSource">Type of the source</typeparam>
/// <typeparam name="TPropType">Type of the property</typeparam>
public class PropertyChangedProxy<TSource, TPropType> where TSource : INotifyPropertyChanged
{
	private readonly Func<TSource, TPropType> _getValueFunc;
	private readonly TSource _source;
	private readonly Action<TPropType> _onPropertyChanged;
	private readonly string _modelPropertyname;

	/// <summary>
	/// Constructor for a property changed proxy
	/// </summary>
	/// <param name="source">The source object to listen for property changes</param>
	/// <param name="selectorExpression">Expression to the property of the source</param>
	/// <param name="onPropertyChanged">Action to take when a property changed was fired</param>
	public PropertyChangedProxy(TSource source, Expression<Func<TSource, TPropType>> selectorExpression, Action<TPropType> onPropertyChanged)
	{
		_source = source;
		_onPropertyChanged = onPropertyChanged;
		// Property "getter" to get the value
		_getValueFunc = selectorExpression.Compile();
		// Name of the property
		var body = (MemberExpression)selectorExpression.Body;
		_modelPropertyname = body.Member.Name;
		// Changed event
		_source.PropertyChanged += SourcePropertyChanged;
	}

	private void SourcePropertyChanged(object sender, PropertyChangedEventArgs e)
	{
		if (e.PropertyName == _modelPropertyname)
		{
			_onPropertyChanged(_getValueFunc(_source));
		}
	}
}