A recent solution I have come up with is to encapsulate the event dispatch logic into a dedicated class.
The class has a public method called Handle which has the same signature as the PropertyChangedEventHandler delegate meaning 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 meaning it can be used without having to create subclasses.
The class looks like this:
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 a property changed event handler like this:
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;
The PropertyChangedHandler takes care of checking the PropertyName of the PropertyChangedEventArgs and ensures that handler is invoked by the right property changes.
Notice that the PropertyChangedHandler also accepts a predicate so that the handler delegate can be conditionally dispatched. The class also allows you to specify multiple properties so that a single handler can be bound to multiple properties in one go.
This can easily be extended using some extensions methods for more convenient handler registration which allows you to create the event handler and subscribe to the PropertyChanged event in a single method call and specify the properties using expressions instead of strings to achieve something that looks like this:
aViewModel.OnPropertyChanged(
handler: p => handlerMethod(),
condition: p => handlerCondition,
properties: aViewModel.GetProperties(
p => p.Foo,
p => p.Bar,
p => p.Baz
)
);
This is basically saying that when either the Foo, Bar or Baz properties change handlerMethod will be invoked if handlerCondition is true.
Overloads of the OnPropertychanged method are provided to cover different event registration requirements.
If, for example, you want to register a handler that is called for any property changed 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 is always executed but only for a single specific property change you can do the following:
aViewModel.OnPropertyChanged(
handler: p => handlerMethod(),
properties: aViewModel.GetProperties(p => p.Foo)
);
I have found this approach very useful when writing WPF MVVM applications. Imagine you have a scenario where you want to invalidate a command when any of three properties change. Using the normal method you would have 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 to update the event handler to select the correct properties.
Using the PropertyChangedHandler class specified above you can achieve the same result with the following:
aViewModel.OnPropertyChanged(
handler: p => FooBarBazCommand.Invalidate(),
properties: aViewModel.GetProperties(
p => p.Foo,
p => p.Bar,
p => p.Baz
)
);
This now has compile-time safety so If any of the viewModel properties are renamed the program will fail to compile.