Binding to Observables in XAML - Part2: WPF

This blog contains the implementation of an observable binding in WPF using a MarkupExtension. I you want to know more about the reasoning behind this, take a look at this blog.

You can find the code here.

For other versions please visit:

The Plan

Before making a plan about observable bindings, let's think back about a normal classical binding:

<TextBox Text="{Binding Name}"/>

This creates a Binder object that synchronizes the DependencyProperty with a Property from the ViewModel. The binding object knows when the DependencyProperty changes by registering a handler using DependencyPropertyDescriptor.AddValueChanged. Likewise, the binding knows about the updates from the ViewModel by registering to the PropertyChanged event.

Normally a MarkupExtension simply returns a value when ProvideValue() is called. But the BindingExtension uses it to hook up the previously mentioned event handlers. We are going to do the same thing here. Except, we're going to subscribe to an observable.

The idea is to end up with code like this:

View

<TextBlock Text="{o:Bind StringObservable, Mode=OneTime}"/>
<TextBox Text="{o:Bind StringObservable, Mode=OneWay}"/>
<TextBox Text="{o:Bind StringObserver, Mode=OneWayToSource}"/>
<TextBox Text="{o:Bind StringSubject, Mode=TwoWay}"
         ToolTip="{o:Bind StringObservable, Mode=OneWay}"/>

ViewModel

public IObservable<string> StringObservable{ get; set; } 
public IObserver<string> StringObserver{ get; set; } 
public Subject<string> StringSubject { get; set; } 

Our binding should be able to bind to any DependencyProperty and support all BindingModes in WPF. To limit the scope, we're only going to consider DataContext and ignore any other sources.

Creating the MarkupExtension

Let's get started by creating our MarkupExtension:

[MarkupExtensionReturnType(typeof(object))]
class BindExtension : MarkupExtension
{
  private DependencyObject bindingTarget;
  private DependencyProperty bindingProperty;

  [ConstructorArgument("path")]
  public PropertyPath Path { get; set; }
  public BindingMode Mode { get; set; }

  public BindExtension() { }
  public BindExtension(PropertyPath path)
    : this()
  {
    Path = path;
  }

  public override object ProvideValue(IServiceProvider serviceProvider)
  {
    ...
  }
}

We create a class called BindExtension. By following the name convention for MarkupExtensions, that means that in XAML we can simply write o:Bind where o is a prefix for the namespace. We continue by creating a Path and a Mode property. We can simply re-use the PropertyPath and BindingMode Types from the original BindingExtension. Notice how we set Path as the constructor argument. This way, you don't have to write Path in the expression.

<TextBox Text="{o:Bind Path=Name}"/>
<TextBox Text="{o:Bind Name}"/> <!-- "Name" is used as a constructor argument -->

If we want to listen to an observable and update the DependencyProperty, we need to get a reference to both. Let's start with retrieving the DependencyProperty and it's DependencyObject using the IProvideValueTarget Service.

public override object ProvideValue(IServiceProvider serviceProvider)
{
  var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

  if (valueProvider != null)
  {
    bindingTarget = valueProvider.TargetObject as DependencyObject;
    bindingProperty = valueProvider.TargetProperty as DependencyProperty;

    ...
  }

  return bindingProperty.DefaultMetadata.DefaultValue;
}

ProvideValue() needs to return a value immediately. We can simply return the default value for the current DependencyProperty.

Great, that's all the information we need from the DependencyProperty's side. Let's get a reference to the DataContext.

public override object ProvideValue(IServiceProvider serviceProvider)
{
  var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

  if (valueProvider != null)
  {
    bindingTarget = valueProvider.TargetObject as DependencyObject;
    bindingProperty = valueProvider.TargetProperty as DependencyProperty;

    //DataContext
    var dataContextSource = FindDataContextSource(bindingTarget);
    dataContextSource.DataContextChanged += DataContextSource_DataContextChanged;
    var dataContext = dataContextSource?.DataContext;

    SetupBinding(dataContext);
  }

  //return default
  return bindingProperty.DefaultMetadata.DefaultValue;
}

The DataContext might change over time that's why we need to register for the DataContextChanged event. There, we will remove any previous setup and call SetupBinding() for the new DataContext. More about SetupBinding() in the next part.

FindDataContextSource() retrieves the DataContext from the bindingTarget. If the bindingTarget is a FrameworkElement, then it has a DataContext property. But not all DependencyObjects are FrameworkElements. Consider the following case:

<Rectangle>
   <Rectangle.RenderTransform>
     <RotateTransform Angle="{Binding Value}" />
   </Rectangle.RenderTransform>
</Rectangle>

RotateTransform is not a FrameworkElement, that means it needs to bind to the DataContext of the Rectangle. Here is the implementation of FindDataContextSource():

public FrameworkElement FindDataContextSource(DependencyObject d)
{
  DependencyObject current = d;
  while (!(current is FrameworkElement) && current != null)
  {
    current = LogicalTreeHelper.GetParent(d);
  }

  return (FrameworkElement)current;
}

It travels through the Logical Tree until it finds a FrameworkElement.

Once we have the FrameworkElement and its DataContext, we need to resolve the path of the expression. We can use this little helper method:

private object GetObjectFromPath(object dataContext, string path)
{
  var properties = path.Split('.');
  var current = dataContext;

  foreach (var prop in properties)
  {
    if (current == null) break;
    current = current.GetType().GetProperty(prop).GetValue(current);
  }

  return current;
}

Unfortunately, it requires reflection. But this is only done once per Binding as long as the DataContext stays the same. This is exactly what the classical BindingExtension does.

Finally, let's have a look at what SetupBinding does:

private void SetupBinding(object source)
{
  //sanity check
  if (source == null) return;

  var boundObject = GetObjectFromPath(source, Path.Path);
  if (boundObject == null) return;

  //set default BindingMode 
  if (Mode == BindingMode.Default)
  {
    if (bindingProperty.GetMetadata(bindingTarget) is FrameworkPropertyMetadata metadata
        && metadata.BindsTwoWayByDefault)
    {
      Mode = BindingMode.TwoWay;
    }
    else
    {
      Mode = BindingMode.OneWay;
    }
  }

  //bind to Observable and update property
  if (Mode == BindingMode.OneTime || Mode == BindingMode.OneWay || Mode == BindingMode.TwoWay)
  {
    SetupListenerBinding(boundObject);
  }

  //send property values to Observer
  if (Mode == BindingMode.OneWayToSource || Mode == BindingMode.TwoWay)
  {
    SetupEmitBinding(boundObject);
  }

}

It basically goes through four steps:

  1. It receives the DataContext and resolves the Path
  2. If the BindingMode is not set, it looks up the default BindingMode for this DependencyProperty
  3. If the BindingMode indicates we need to update the View from the ViewModel, we assume that the bound object is an observable and set up a listening mechanism to update the view.
  4. If the BindingMode indicates we need to update the ViewModel from the View, we assume that the bound object is an observer and set up an emitter to send new values to the observer.

ViewModel → View

In this part we will create a mechanism to update our View based upon an observable. If we think back about classical Binding, the ViewModel usually implements INotifyPropertyChanged. So there, the BindingExtension simply subscribes to the PropertyChanged event. And when that event fires, it updates the view accoridingly. What we need to do is replace this event with a subscription to our observable.

Consider the following piece of code:

private void SetupListenerBinding(object observable)
{
  SubscribePropertyForObservable((IObservable<object>)observable, bindingTarget, bindingProperty);
}

private void SubscribePropertyForObservable(IObservable<object> observable, 
                             DependencyObject d, DependencyProperty property)
{
  if (observable == null) return;
  
  if (Mode == BindingMode.OneTime)
  {
    observable = observable.Take(1);
  }

  listenSubscription = observable.ObserveOn(SynchronizationContext.Current)
                                 .Subscribe(val => d.SetValue(property, val));
}

This code brings us a long way. You can see that whenever a new value is emitted by the observable, the DependencyObject will be updated by using SetValue().

Whether ObserveOn(SynchronizationContext.Current) should be called here, is up for debate. By adding this, no matter which thread sends the new value, the DependencyObject will always be updated on the UI Thread. But maybe that's the responsibility of the ViewModel and not this Binding. The classical Binding does not do this, but it is a simple small addition here.

But, there is a problem.

/ow_no.png

We assumed that our observable can be cast to type IObservable<object>. Which seems to be ok. SetValue() receives an object anyway, so we don't care about the exact type. Plus IObservable<in T> is covariant, which means: If A inherits B then IObservable<A> inherits IObservable<B>. That's great since everything inherits object this should work.

But no, it doesn't.

Covariance and contravariance in generic type parameters are supported for reference types, but they are not supported for value types. (source)

So that means this mechanism doesn't work for e.g. double and int.

In order to fix this, we will need to exact type of the observable. SubscribePropertyForObservable becomes:

private void SubscribePropertyForObservable<TProperty>(IObservable<TProperty> observable, 
                             DependencyObject d, DependencyProperty property)
{
  if (observable == null) return;

  if (Mode == BindingMode.OneTime)
  {
    observable = observable.Take(1);
  }

  listenSubscription = observable.ObserveOn(SynchronizationContext.Current)
                                 .Subscribe(val => d.SetValue(property, val));
}

And SetupListenerBinding becomes:

private void SetupListenerBinding(object observable)
{
  //IObservable<T> --> typeof(T)
  var observableGenericType = observable.GetType()
    .GetInterfaces()
    .Single(type => type.IsGenericType && type.GetGenericTypeDefinition() == (typeof(IObservable<>)))
    .GetGenericArguments()[0];

  //get SubscribePropertyForObservable<> method
  MethodInfo method = typeof(BindExtension)
    .GetMethod(nameof(SubscribePropertyForObservable), BindingFlags.NonPublic | BindingFlags.Instance);

  //SubscribePropertyForObservable<> --> SubscribePropertyForObservable<T> 
  MethodInfo generic = method.MakeGenericMethod(observableGenericType);

  //invoke generic method SubscribePropertyForObservable<T>(observable, bindingTarget, bindingProperty)
  generic.Invoke(this, new object[] { observable, bindingTarget, bindingProperty });
}

As you can see, we need reflection to get the exact Type. First we get T from the implemented interface IObservable<T> and then we need to call SubscribePropertyForObservable<T> with that type. That also means we have to construct and call SubscribePropertyForObservable<T> at runtime.

Once again we are forced to use reflection. But it does not impact the performance of the subscription, only the initial set up.

The listenSubscription is kept in a global field, so it can be cleaned up when the DataContext changes. When a Visual is removed from the Visual Tree, its DataContext is set to null. So relying on the DataContextChanged event should suffice, and cause no memory leaks.

View → ViewModel

In this last part we will send changes in our view to an observer. In classical Binding, when a DependencyProperty is modified, it simply sets the bound property on the DataContext. Here, this Property will be an observer, and we'll have to call OnNext() when a new value arrives.

It will take two steps to reach our goal:

  1. Create an observable for the DependencyProperty
  2. Subscribe the observer to that observable

First of all, how do you know that a DependencyProperty received a new value? In WPF you can use AddValueChanged:

DependencyPropertyDescriptor.AddValueChanged(object component, EventHandler handler) //start listening
DependencyPropertyDescriptor.RemoveValueChanged(object component, EventHandler handler) //stop listening

This basically works as a regular event. We can turn that into an observable as so:

public static IObservable<TProperty> Observe<TComponent, TProperty>(this TComponent component, 
                                                         DependencyProperty dependencyProperty)
where TComponent : DependencyObject
{
  return Observable.Create<TProperty>(observer =>
  {
    //register event handler
    EventHandler update = (sender, args) => observer.OnNext((TProperty)((TComponent)sender).GetValue(dependencyProperty));
    var property = DependencyPropertyDescriptor.FromProperty(dependencyProperty, typeof(TComponent));
    property.AddValueChanged(component, update);

    //unregister
    return Disposable.Create(() => property.RemoveValueChanged(component, update));
  });
}

This is an extension method that can be applied to any DependencyObject. The first part indicates that when the event is raised, we should retrieve the value (using GetValue()) and then send that to the observer (observer.OnNext()). The return statement is important for the clean up. When Dispose is called on the created observable, it will unregister from the event.

Subscribing to the observable is quite easy. However, we do need the exact type again for the same reason as before.

private void SubScribeObserverForProperty<TProperty>(IObserver<TProperty> observer, DependencyObject d, 
                                                           DependencyProperty propertyToMonitor)
{
  if (propertyToMonitor.OwnerType.IsAssignableFrom(d.GetType()) && observer != null)
  {
    emitSubscription = d.Observe<DependencyObject, TProperty>(propertyToMonitor)
                        .Subscribe(observer);
  }
}

Once more, emitSubscription is kept in a global field, so it can be cleaned up when the DataContext changes.

Finally, we need to get the type of IObsever<T>. This is very easy since we can just use bindingProperty.PropertyType. Calling the generic method means constructing it at runtime using reflection and then invoking it. Just like we did in the previous part.

private void SetupEmitBinding(object observer)
{
  //get SubScribeObserverForProperty<> method
  MethodInfo method = typeof(BindExtension)
    .GetMethod(nameof(SubScribeObserverForProperty), BindingFlags.NonPublic | BindingFlags.Instance);

  //SubScribeObserverForProperty<> --> SubScribeObserverForProperty<T>
  MethodInfo generic = method.MakeGenericMethod(bindingProperty.PropertyType);

  //invoke generic method SubScribeObserverForProperty<T>(observer, bindingTarget, bindingProperty)
  generic.Invoke(this, new object[] { observer, bindingTarget, bindingProperty });
}

The Result

To test our BindingExtension, I made this very simple WPF application:

/xaml_binding_wpf.gif

The View and ViewModel are as follows:

View

<StackPanel x:Name="sp">
  <TextBlock Text="{o:Bind StringStream, Mode=OneWay}"/>
  <TextBlock Text="{o:Bind StringSubject, Mode=OneTime}"/>
  <TextBlock Text="{o:Bind StringSubject, Mode=OneWay}"/>
  <TextBox Text="{o:Bind StringSubject, Mode=OneWay}"/>
  <TextBox Text="{o:Bind StringSubject, Mode=OneWayToSource}"/>
  <TextBox Text="{o:Bind StringSubject, Mode=TwoWay}"
           ToolTip="{o:Bind StringSubject, Mode=OneWay}"/>

  <Slider Value="{o:Bind DoubleSubject, Mode=TwoWay}" Maximum="100"/>
  <TextBlock Text="{o:Bind DoubleSubject, Mode=OneWay}"/>

  <Button Content="remove children" Click="Button_Click"/>
</StackPanel>

ViewModel

class MainViewModel
{
  public IObservable<string> StringStream { get; set; }
    = Observable.Interval(TimeSpan.FromSeconds(1)).Select(i => i.ToString());

  public Subject<string> StringSubject { get; set; } = new Subject<string>();

  public Subject<double> DoubleSubject { get; set; } = new Subject<double>();
}

I remove all bound controls at the end to see it they are cleaned up correctly.

I'm very happy with this result. The code the developer has to write is elegant, and the performance is on par with the classical BindingExpression.

Any thoughts or constructive criticism is welcome.

Once again, You can find the code here.