I noticed this rated high on the WPF feature suggestion list. For a while now RX is a fantastic choice.
You need a utility class (ExpressionHelper which I’ve mentioned before by the name PropertyHelper) to extract property names from a lambda and turn them into strings. A simple base class to raise the INotifyPropertyChanged events (NotifyingBase). Some extension methods to INotifyPropertyChanged using RX (ObservableExtensions) so you can observe the properties. And finally some code to hook it altogether, be it in a view model or in my example, a controller. Hopefully this’ll help some people out on that forum.
Download this code (185.24 kb) (WPF 4, rx v1.0.2787.0 (included)).
First the extension to INotifyPropertyChanged.
using System;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
namespace PropertyObservation
{
public static class ObservableExtensions
{
public static IObservable<TValue> ObserveProperty<T, TValue>(
this T source,
Expression<Func<T, TValue>> propertyExpression
)
where T : INotifyPropertyChanged
{
return source.ObserveProperty(propertyExpression, false);
}
public static IObservable<TValue> ObserveProperty<T, TValue>(
this T source,
Expression<Func<T, TValue>> propertyExpression,
bool observeInitialValue
)
where T : INotifyPropertyChanged
{
var memberExpression = (MemberExpression)propertyExpression.Body;
var getter = propertyExpression.Compile();
var observable = from evt in Observable
.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>
(h => (s, e) => h(s, e),
h => source.PropertyChanged += h,
h => source.PropertyChanged -= h)
where evt.EventArgs.PropertyName == memberExpression.Member.Name
select getter(source);
if (observeInitialValue)
return observable.Merge(Observable.Return(getter(source)));
return observable;
}
}
}
Then the base class for anything that wants to support INotifyPropertyChanged.
using System;
using System.ComponentModel;
using System.Linq.Expressions;
namespace PropertyObservation
{
public class NotifyingBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
// some like this better as its easier to generate
protected void RaisePropertyChanged<T>(Expression<Func<T>> expression)
{
var propertyName = ExpressionHelper.GetPropertyName(expression);
OnPropertyChanged(propertyName);
}
// some prefer this as its more typed
protected void RaisePropertyChanged<T>(Expression<Func<T, Object>> expression)
{
var propertyName = ExpressionHelper.GetPropertyName(expression);
OnPropertyChanged(propertyName);
}
}
}
The above uses ExpressionHelper, a class that I use in nearly everything these days (similar version mentioned by me previously as the PropertyHelper class). Also has a handy GetMethodName(). Here it is:
using System;
using System.Linq.Expressions;
namespace PropertyObservation
{
public class ExpressionHelper
{
public static string GetPropertyName<T>(Expression<Func<T>> expression)
{
return GetNameFromLambda(expression);
}
public static string GetPropertyName<T>(Expression<Func<T, Object>> expression)
{
return GetNameFromLambda(expression);
}
public static string GetMethodName<T>(Expression<Action<T>> expression)
{
return GetNameFromLambda(expression);
}
private static string GetNameFromLambda(LambdaExpression lambda)
{
MemberExpression memberExpression = null;
if (lambda.Body is UnaryExpression)
{
var unaryExpression = lambda.Body as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else if (lambda.Body is MemberExpression)
memberExpression = lambda.Body as MemberExpression;
if (memberExpression != null)
return memberExpression.Member.Name;
if (lambda.Body is MethodCallExpression)
{
var methodCallExpression = lambda.Body as MethodCallExpression;
return methodCallExpression.Method.Name;
}
throw new ArgumentException(String.Format("Expression '{0}' did not provide a property or method name.", lambda));
}
}
}
A view model example
namespace PropertyObservation
{
public class MainWindowViewModel : NotifyingBase
{
private string _userName;
public string UserName
{
get { return _userName; }
set
{
_userName = value;
// more strongly typed
RaisePropertyChanged<MainWindowViewModel>(vm => vm.UserName);
}
}
private string _password;
public string Password
{
get { return _password; }
set
{
_password = value;
// bit looser, more room for setting an incorrect property
RaisePropertyChanged(() => Password);
}
}
private string _results;
public string Results
{
get { return _results; }
set
{
_results = value;
RaisePropertyChanged(() => Results);
}
}
}
}
And finally some code in a controller (or where ever you'd normally put this type of controlling code, some say the view model).
using System;
using System.Linq;
namespace PropertyObservation
{
public class MainWindowController
{
private readonly MainWindowViewModel _mainWindowViewModel;
public MainWindowController(MainWindowViewModel mainWindowViewModel)
{
_mainWindowViewModel = mainWindowViewModel;
}
public void Start()
{
var userNameChanged = _mainWindowViewModel.ObserveProperty(vm => vm.UserName, true);
var passwordChanged = _mainWindowViewModel.ObserveProperty(vm => vm.Password, true);
var bothChanged = userNameChanged.CombineLatest(passwordChanged, (username, password) => String.Format("You typed {0} {1}", username, password));
var disposable = bothChanged.Subscribe(
message =>
{
_mainWindowViewModel.Results = message;
}
);
// TODO dispose your event wireup at some time if required.
}
}
}
The xaml is just standard WPF data binding, check out the download if required. Hope this helps someone out.