Handling PropertyChanged in a Safe Way

There have been many articles on how to use reflection and LINQ to raise PropertyChanged events in a safe way, without using strings.

But is there a way to kill PropertyChanged events in a safe manner? I'm currently doing this

void model_PropertyChanged(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case "Property1": ... case "Property2": ... .... } } 

Is there a way to avoid hard-coding strings in a switch statement to handle various properties? Some similar LINQ or reflection based approach?

+7
c # events silverlight inotifypropertychanged
source share
5 answers

Let's declare a method that can turn a lambda expression into a Reflection PropertyInfo object ( from my answer here ):

 public static PropertyInfo GetProperty<T>(Expression<Func<T>> expr) { var member = expr.Body as MemberExpression; if (member == null) throw new InvalidOperationException("Expression is not a member access expression."); var property = member.Member as PropertyInfo; if (property == null) throw new InvalidOperationException("Member in expression is not a property."); return property; } 

And then let's use it to get property names:

 void model_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == GetProperty(() => Property1).Name) { // ... } else if (e.PropertyName == GetProperty(() => Property2).Name) { // ... } } 

Unfortunately, you cannot use the switch because property names are no longer compile-time constants.

+3
source share

With C # 6.0 you can use nameof . You can also refer to a class property without instantiating this class.

 void model_PropertyChanged(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case nameof(ClassName.Property1): ... case nameof(ClassName.Property2): ... .... } } 
+2
source share

The Josh Smith MVVM Foundation includes a PropertyObserver class that does what you want.

+1
source share

I avoid switching by combining a command pattern and some expression logic. You encapsulate the case action in a command. I will illustrate this with the Model View Controller structure. real world code is winforms but that is the same idea

The example loads a tree into the view when the Tree property is set in the model.

custom icommand

 void Execute(); string PropertyName { get; } 

Specific team

  public TreeChangedCommand(TreeModel model, ISelectTreeView selectTreeView,Expression<Func<object>> propertyExpression ) { _model = model; _selectTreeView = selectTreeView; var body = propertyExpression.Body as MemberExpression; _propertyName = body.Member.Name; } 

constructor controller

  //handle notify changed event from model _model.PropertyChanged += _model_PropertyChanged; //init commands commands = new List<ICommand>(); commands.Add(new TreeChangedCommand(_model,_mainView,()=>_model.Tree)); 

propertyChanged handler

 void _model_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { //find the corresponding command and execute it. (instead of the switch) commands.FirstOrDefault(cmd=>cmd.PropertyName.Equals(e.PropertyName)).Execute(); } 
+1
source share

The last solution I came up with is to encapsulate the logic of sending events to a dedicated class.

The class has an open Handle method, which has the same signature as the PropertyChangedEventHandler delegate, which means that it can be subscribed to the PropertyChanged event of any class that implements the INotifyPropertyChanged interface.

The class accepts delegates, like the often used DelegateCommand used by most WPF implementations, which means it can be used without subclassing.

The class is as follows:

 public class PropertyChangedHandler { private readonly Action<string> handler; private readonly Predicate<string> condition; private readonly IEnumerable<string> properties; public PropertyChangedHandler(Action<string> handler, Predicate<string> condition, IEnumerable<string> properties) { this.handler = handler; this.condition = condition; this.properties = properties; } public void Handle(object sender, PropertyChangedEventArgs e) { string property = e.PropertyName ?? string.Empty; if (this.Observes(property) && this.ShouldHandle(property)) { handler(property); } } private bool ShouldHandle(string property) { return condition == null ? true : condition(property); } private bool Observes(string property) { return string.IsNullOrEmpty(property) ? true : !properties.Any() ? true : properties.Contains(property); } } 

You can then register the property handler handler as follows:

 var eventHandler = new PropertyChangedHandler( handler: p => { /* event handler logic... */ }, condition: p => { /* determine if handler is invoked... */ }, properties: new string[] { "Foo", "Bar" } ); aViewModel.PropertyChanged += eventHandler.Handle; 

PropertyChangedHandler takes care of checking the PropertyName PropertyChangedEventArgs and ensures that the handler is triggered by changes to the properties of the right.

Note that PropertyChangedHandler also accepts a predicate, so that a handler delegate can be conditionally dispatched. The class also allows you to specify multiple properties so that one handler can bind to multiple properties at a time.

This can be easily expanded using some extension methods for more convenient registration of the handler, which allows you to create an event handler and subscribe to the PropertyChanged event in one method call and specify properties using expressions instead of strings to achieve something, it looks like this:

 aViewModel.OnPropertyChanged( handler: p => handlerMethod(), condition: p => handlerCondition, properties: aViewModel.GetProperties( p => p.Foo, p => p.Bar, p => p.Baz ) ); 

This basically means that when the Foo , Bar or Baz handlerMethod , if handlerCondition true.

OnPropertychanged method OnPropertychanged provided to cover various event logging requirements.

If, for example, you want to register a handler that is called for any changed property event and is always executed, you can simply do the following:

 aViewModel.OnPropertyChanged(p => handlerMethod()); 

If, for example, you want to register a handler that always runs, but only for one specific property change, you can do the following:

 aViewModel.OnPropertyChanged( handler: p => handlerMethod(), properties: aViewModel.GetProperties(p => p.Foo) ); 

I found this approach very useful when writing WPF MVVM applications. Imagine that you have a scenario where you want to invalidate a command when any of the three properties change. Using the usual method, you will need to do something like this:

 void PropertyChangedHandler(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case "Foo": case "Bar": case "Baz": FooBarBazCommand.Invalidate(); break; .... } } 

If you change the name of any of the viewModel properties, you will need to remember the event handler update to select the correct properties.

Using the PropertyChangedHandler class specified above, you can get the same result with the following:

 aViewModel.OnPropertyChanged( handler: p => FooBarBazCommand.Invalidate(), properties: aViewModel.GetProperties( p => p.Foo, p => p.Bar, p => p.Baz ) ); 

We now have security at compile time. If any of the viewModel properties is renamed, the program will not be able to compile.

+1
source share

All Articles