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.
PropertyChangedEventArgs
at all and instead declaring one of your own that contains thePropertyInfo
object instead of a string.) – Feld