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