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));
}
}
}