Creating a strongly typed reactive wrapper to INotifyPropertyChanged
Practical Ugliness
INotifyPropertyChanged
is a great, built-in, way for property change notification to work in the WPF/Silverlight world. Attempting to use it from staticly typed code, however, gets messy:
SomeViewModel viewModel;
viewModel.PropertyChanged += (s,e) =>
{
if (e.PropertyName == "TheProperty")
GetToWork(viewModel.TheProperty);
};
Things get even worse when we try to make this reactive:
var propertyChangedEvents = Observable.FromEvent(
h => new PropertyChangedEventHandler(h),
h => viewModel.PropertyChanged += h,
h => viewModel.PropertyChanged -= h);
propertyChangedEvents
.Where(x => x.PropertyName == "TheProperty")
.Select(x => viewModel.TheProperty)
.Subscribe(GetToWork);
An Expressive Solution
The extension method below allows you to specify the property you want to watch using an Expression<Func>
, keeping things nice for the compiler:
Edit: Updated once it was tested (and simplified)
public static class NotifyPropertyChangeReactiveExtensions
{
// Returns the values of property (an Expression) as they change,
// starting with the current value
public static IObservable<TValue> GetPropertyValues<TSource, TValue>(
this TSource source, Expression<Func<TSource, TValue>> property)
where TSource : INotifyPropertyChanged
{
MemberExpression memberExpression = property.Body as MemberExpression;
if (memberExpression == null)
{
throw new ArgumentException(
"property must directly access a property of the source");
}
string propertyName = memberExpression.Member.Name;
Func<TSource, TValue> accessor = property.Compile();
return source.GetPropertyChangedEvents()
.Where(x => x.EventArgs.PropertyName == propertyName)
.Select(x => accessor(source))
.StartWith(accessor(source));
}
// This is a wrapper around FromEvent(PropertyChanged)
public static IObservable<IEvent<PropertyChangedEventArgs>>
GetPropertyChangedEvents(this INotifyPropertyChanged source)
{
return Observable.FromEvent<PropertyChangedEventHandler,
PropertyChangedEventArgs>(
h => new PropertyChangedEventHandler(h),
h => source.PropertyChanged += h,
h => source.PropertyChanged -= h);
}
}
GetPropertyValues
returns an IObservable
of the values of the property as they change, starting with the current value.
You can then use it like so:
viewModel.GetPropertyChangeValues(x => x.TheProperty)
.Subscribe(GetToWork);
I hope this method can be as useful to you as it has been to me.