What's this article about?
Consider the following nested member expression:
this.Person.Employer.Address
Wouldn't it be great if you could write something like
Observe(() => this.Person.Employer.Address, address => UpdateAddress(address));
with the meaning that UpdateAddress
will be called automatically every time the expression changes?
Note that the change could come from any level, from Person
to
Address
itself. This is exactly what XAML bindings do, and this article explains how to do it in code.
I'm using Silverlight - I expect this to work in WPF and WinRT as well, but I haven't tried this yet.
Why does this matter?
When coding in the MVVM paradigm one needs to implement view models that are bound against views written in XAML.
I find that those view models often themselves need to bind against other sources. There are two common scenarios:
- The view consists of subviews requiring respective sub-view-models and the outer view model needs to be notified of changes in such a sub-view-model.
- The models which contain the actual data and which are backing the view models have themselves "view model" traits - I'll explain what I mean by that with an example:
Take RIA Services. With this framework, (some) of your models will be auto-generated entity classes. They can actually play the role of view models themselves in simple cases,
but let's assume one has a separate view model. When such an entity is updated by user interaction or a load operation, the view model sometimes must
be notified of the change to update accordingly. Please take a look at the following diagram:
The many entity classes are often a given: They simply closely model how the data is organized. Now imagine the view should display a prominent visual
in the case where the address refers to the same house as the currently logged-in user. To implement that, one would need to track changes of this member expression in the view model:
this.Person.Employer.Address
And a change could likely be triggered from:
- the user by editing text or
- the user by rejecting the current changes or
- a completion of a load operation.
Wiring up all these probably very distinct code paths to trigger some update method in the view model is tedious and error prone.
Why can't we just observe the property path just like we do in XAML bindings? What we want to write is simply something like "{Binding Person.Employer.Address}
".
How is it done?
The key is to use the Binding
object programmatically. This means that the behavior of our tool will be the same as that of XAML bindings and it will work in exactly the same cases.
Since the Binding
object expects the property path as a string literal and we want to use type- and name-safe lambda expressions, the first step is a tool
to generate property paths from such expressions.
public class RootedPropertyPath<T>
{
public Object Target { get; set; }
public String Path { get; set; }
public Binding ToBinding(BindingMode mode = BindingMode.TwoWay)
{
return new Binding(Path) { Source = Target, Mode = mode };
}
public static implicit operator System.Windows.PropertyPath(RootedPropertyPath<T> self)
{
return new System.Windows.PropertyPath(self.Path);
}
public static implicit operator String(RootedPropertyPath<T> self)
{
return self.Path;
}
}
public static class RootedPropertyPath
{
public static RootedPropertyPath<T> Create<T>(Expression<Func<T>> expr)
{
Expression currentExpression = expr.Body;
List<String> lst = new List<String>();
ConstantExpression ce;
while (true)
{
ce = currentExpression as ConstantExpression;
var me = currentExpression as MemberExpression;
if (ce != null) break;
if (me == null)
throw new Exception(String.Format(
"Unexpected expression type {0} in lambda.", expr.GetType()));
lst.Add(me.Member.Name);
currentExpression = me.Expression;
}
lst.Reverse();
return new RootedPropertyPath<T>() { Path = String.Join(".", lst), Target = ce.Value };
}
}
Note that RootedPropertyPath<T>
is already slightly more than a type-safe property path: We also collect the base object,
the "target" of the binding right from the expression - hence the "rooted" in the name.
A path can now be constructed with
var rootedPath = RootedPropertyPath.Create(() => this.Person.Employer.Address)
and then
var binding = rootedPath.ToBinding()
gives us the binding we need to observe the nested member expression.
The Create method basically consists of the common trickery used for implementing
INotifyPropertyChanged
in a a type- and name-safe way.
The next step is to bind something. In order to do that, we need a DependencyProperty
of the right type and bind that to the binding we have. That's what the following tool is for:
public static class ObservableDependencyValue
{
public static ObservableDependencyValue<T> Create<T>(
Expression<Func<T>> expr, BindingMode mode = BindingMode.OneWay)
{
return new ObservableDependencyValue<T>().Bind(expr, mode);
}
}
public class ObservableDependencyValue<T> : DependencyObject
{
#region T ObservableDependencyValue<T>.Value = default(T)
#region Boilerplate
public T Value
{
get { return (T)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(T),
typeof(ObservableDependencyValue<T>), new PropertyMetadata(default(T), StaticHandleValueChanged));
static void StaticHandleValueChanged(DependencyObject self, DependencyPropertyChangedEventArgs args)
{
((ObservableDependencyValue<T>)self).HandleValueChanged((T)args.OldValue, (T)args.NewValue);
}
#endregion
void HandleValueChanged(T oldValue, T value)
{
Notify();
}
#endregion
public ObservableDependencyValue()
{
}
public ObservableDependencyValue(BindingBase bindingBase)
{
Bind(bindingBase);
}
public ObservableDependencyValue<T> Bind(BindingBase bindingBase)
{
BindingOperations.SetBinding(this, ValueProperty, bindingBase);
return this;
}
public ObservableDependencyValue<T> Bind(Expression<Func<T>> expr,
BindingMode mode = BindingMode.OneWay)
{
var path = RootedPropertyPath.Create(expr);
return Bind(new Binding(path.Path) { Source = path.Target, Mode = mode });
}
public void Notify()
{
if (ValueChanged != null) ValueChanged(Value);
}
public event Action<T> ValueChanged;
}
ObservableDependencyProperty<T>
exists just for its one property "Value" that can be bound to an arbitrary binding and report changes.
For convenience, the creation of the binding is already built into the class - we just need to pass the member expression itself in the factory method
of ObservableDependencyValue
.
We're now almost there. If we write our view model like this
public class PersonViewModel : OurViewModelBase
{
public PersonViewModel()
{
var observableDependencyProperty = ObservableDependencyValue.Create(() =>
this.Person.Employer.Address);
we have an object that features an event fired on a change of the member expression. This can be used directly:
observableDependencyProperty.ValueChanged += address => UpdateAddress(address);
But there is one major catch: We must make sure that our observableDependencyProperty
will live as long as our view model does. Thus, we need to store a reference in our view model:
observableDependencyPropertyReference = observableDependencyProperty;
}
Object observableDependencyPropertyReference;
}
That is slightly unfortunate - but since it's common practice to have a custom common base for view models, I suggest adding something to register such objects there:
public class OurViewModelBase
{
...
protected void Observe<T>(Expression<Func<T>> expr, Action<T> changed)
{
var odv = ObservableDependencyValue.Create(expr);
odv.ValueChanged += changed;
lifetimeObjects.Add(odv);
}
List<Object> lifetimeObjects = new List<Object>();
...
}
And that is the promised syntax and the conclusion of this article - I hope you find the technique useful.