And now for something completely crazy: Binding without WPF
I was struggling with a rather complex ViewModel (from Model-View-ViewModel or MVVM) that was actually composed of a number of ViewModels.
Most of the problems was internal notification of change - i.e one ViewModel wanted to know when another ViewModel changed. Sounds crazy but I'm comfortable it was a valid scenario. Because manually wiring up to INotifyPropertyChanged is about as much fun as nail-varnishing your eyeballs, I often work around the problem with a series of back references. However, it dawned on me that what I really want is WPF's awesome binding functionality. You know, it can bind deep on the properties of properties or properties and you don't have to worry about releasing event handlers to avoiud memory leaks.
If only we could use it without WPF - right in our ViewModels. And so my crazy experiment began...
From my brief foray into the internals of Binding (using Reflector and some contacts in the WPF product group) it seems that Binding is pretty coupled to DependencyProperties and there's no obvious way to use it without them. However, I managed to get my scenario working with the (semi-proud unveiling) of the BindingObject, which is used like this:
BindingObject bo = new BindingObject(sourceObject, "Property.That.IWant.ToBindTo", e => Changed(e));
And when the 'deep' property "Property.That.IWant.ToBindTo" on the sourceObject changes, the 'Changed' method is fired. The BindingObject is disposable (IDisposable) and this releases the references and unsubscribes the delegate so that everything can get Garbage Collected. Here's my slightly pokey looking implementation.
public class BindingObject : DependencyObject, IDisposable
{
private bool _suppress;
private Action<DependencyPropertyChangedEventArgs> _onChanged;
public BindingObject(object source, string bindingPath, Action<DependencyPropertyChangedEventArgs> onChanged)
{
using (SuppressNotifications())
{
_onChanged = onChanged;
Binding binding = new Binding(bindingPath);
binding.Source = source;
BindingOperations.SetBinding(this, ValueProperty, binding);
}
}
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(BindingObject), new UIPropertyMetadata(null, ValueChangedCallback));
private static void ValueChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = (BindingObject)d;
Action<DependencyPropertyChangedEventArgs> onChanged = instance._onChanged;
if (onChanged != null && !instance._suppress)
{
onChanged(e);
}
}
public void Dispose()
{
BindingOperations.ClearBinding(this, ValueProperty);
_onChanged = null;
}
public IDisposable SuppressNotifications()
{
return new Supresser(this);
}
private class Supresser : IDisposable
{
private BindingObject _bindingObject;
public Supresser(BindingObject bindingObject)
{
_bindingObject = bindingObject;
_bindingObject._suppress = true;
}
public void Dispose()
{
_bindingObject._suppress = false;
}
}
}
However pokey it looks though, it dramatically simplified the scenario I was tackling. It works in unit tests (with the total absence of WPF) too.
Thoughts please?
Originally posted by Josh Twist on 2nd March 2009 here.