WPF - Data-binding a lesson in framework design

|

In WPF we tend to take data-binding for granted. It is just one of those features that just works. In this post I want to briefly bring your attention to how the data-binding system works with regards to non-FrameworkElement derived classes, not because you need to know to be able to use it, but because I think it has all the hallmarks of a good API design. [...snip...]

Consider this XAML:

<Page
  xmlns="
..."
  xmlns:x="..."
  xmlns:sys="clr-namespace:System;assembly=mscorlib">

  <StackPanel x:Name="panel">
    <ListBox x:Name="colors" Margin="10">
      <sys:String>Black</sys:String>
      <sys:String>Red</sys:String>
      <sys:String>Green</sys:String>
      <sys:String>Blue</sys:String>
    </ListBox>

    <TextBlock
        Text="{Binding SelectedItem, ElementName=colors}"
        FontSize="36"
        Margin="10">
      <TextBlock.Foreground>
        <SolidColorBrush
          Color="{Binding SelectedItem, ElementName=colors}" />
      </TextBlock.Foreground>
    </TextBlock>
  </StackPanel>
</Page>

If you put this XAML straight into something like XamlCruncher or Kaxaml (XamPad doesn't quite cut it because of the external reference to MSCoreLib) you end up with something like this:

DataBindingToBrushName

When you click the items in the ListBox, the TextBlock foreground colour changes and so does the text.

Great, data-binding in action - a somewhat contrived example but it works as you would expect. However, things get a little more interesting when you try and achieve the same effect in procedural code.

Setting the TextBlock text is simple enough as TextBlock is derived from FrameworkElement that has a SetBinding method that we can use - we would write code similar to the following:

// Create the binding
Binding colorsBinding = new Binding();
colorsBinding.Source = this.colors;
colorsBinding.Path = new PropertyPath("SelectedItem");

// Create the TextBlock
TextBlock tb = new TextBlock();
tb.FontSize = 36;
tb.Margin = new Thickness(10);

// Bind the TextProperty to the ListBox.SelectedValue
tb.SetBinding(TextBlock.TextProperty, colorsBinding);

This code is considerably more verbose than using the lovely data-binding XAML syntax, but it should be clear what's going on. Here's the equivalent XAML, as we saw above:

<TextBlock
    Text="{Binding SelectedItem, ElementName=colors}"
    FontSize="36"
    Margin="10">

This would render as:

DataBindingToBrushName2

Note the lack of colour, let's do that next. We can use the same Binding class instance as before and apply it to the ColorProperty dependency property on the SolidColorBrush class, then apply that brush to the TextBlock instance, as shown here:

SolidColorBrush brush = new SolidColorBrush();
brush.SetBinding(SolidColorBrush.ColorProperty, colorsBinding);
tb.Foreground = brush;

However, if you try and compile this code your compiler will complain that SolidColorBrush does not have a SetBinding method. This is because SolidColorBrush derives from Brush, which derives from Animatable, and then from Freezable, before we finally hit DependenyObject:

  • DependencyObject
    • Freezable
      • Animatable
        • Brush
          • SolidColorBrush

But TextBlock has a somewhat different lineage:

  • DependencyObject
    • Visual 
      • Animatable
        • UIElement 
          • FrameworkElement
            • TextBlock

So what's the story here, these two objects only have DependencyObject in their common ancestry, and that class does not have the necessary SetBinding method, how can the binding possibly work at all?

If you were to look at the implementation code for the SetBinding method in FrameworkElement the answer would immediately present itself:

public BindingExpressionBase SetBinding(
    DependencyProperty dp,
    BindingBase binding)
{
    return BindingOperations.SetBinding(this, dp, binding);
}

BindingOperations.SetBinding, this static method asks for a target dependency object, a dependency property and a binding instance. So we should be able to re-write our previous code to use this class directly, like this:

SolidColorBrush brush = new SolidColorBrush();
BindingOperations.SetBinding(
    brush, SolidColorBrush.ColorProperty, colorsBinding);
tb.Foreground = brush;

Which works as expected, therefore the visual outcome is the same as above, but this time the effect is achieved by using procedural code:

DataBindingToBrushName

Now we understand what is going on a little better how does this helps us? "So what?" you might ask. You can still write the same XAML there's nothing new there; learning this has not changed a single thing for you when it comes to data-binding, it just works. It is exactly the it just works aspect of the data-binding design I think is soooo important in this implementation.

The designers of the data-binding API put the convenience method (SetBinding) in exactly the right place to make sense for the object model (high cohesion); but they implemented the feature in such a way that it's not tied to that specific scenario, meaning that have enabled many scenarios with only the absolutely minimum requirements or contract being placed on the API, therefore loosely coupled. Brilliant!

Every single day I learn more about WPF and every day I appreciate the quality of the framework design. Enjoy and learn from a great design, I know I will.

1 comment:

Tim Hibbard said...

Very helpful post. Thanks!