Everything you wanted to know about databinding in WPF, Silverlight and WP7 (Part Two)

Introduction

This is the second post in my series about databinding in Silverlight and WPF. In the first post I looked at how you wire-up UI controls to a model in the absence of a databinding framework. I showed how databindings can be created in code-behind, removing the need for the various event handlers that the manual method requires, resulting in more readable code, where the connection between a UI control and a model property is all in one place. In this blog post I'll look at how bindings can be defined in XAML, providing a more concise and flexible method of wiring model objects to your view.

The rough outline for this series is as follows:

  • Part One - Life before binding, INotifyPropertyChanged and creating bindings in code-behind
  • Part Two (this one) - The binding markup extensions, the DataContext and path syntax
  • Part Three - Other binding sources, ElementName, TemplatedParent, TemplateBinding
  • Part Four - Value converters
  • Part Five - List binding
  • ...

Code-behind binding: a re-cap

The previous blog post worked through the creation of a very simple application which creates a model object that represents an event (INotifyPropertyChanged implementation omitted for clarity)

/// <summary>
/// A simple model object that represents an event
/// </summary>
public class EventModel : INotifyPropertyChanged
{
  /// <summary>
  /// Gets / sets the event title
  /// </summary>
  public string Title { ...  }


  /// <summary>
  /// Gets / sets the date of this event
  /// </summary>
  public DateTime Date   { ...  }
}

And render it with the following UI:

The XAML for the UI is as follows:

<Grid>
  ...
  <TextBlock Text="Name:"
             Grid.Row="1" />
  <TextBox x:Name="EventTitle"
            Grid.Row="1" Grid.Column="1"/>

  <TextBlock Text="Date:"
             Grid.Row="2"/>
  <sdk:DatePicker x:Name="EventDate"
                  Grid.Row="2" Grid.Column="1"/>
</Grid>

The previous blog post wired up the above UI by creating bindings in code-behind:

public MainPage()
{
  InitializeComponent();

  // create a model object
  _event = new EventModel()
  {
    Date = new DateTime(2011, 7, 1),
    Title = "Silverlight User Group"
  };

  // bind the Date to the UI
  EventDate.SetBinding(DatePicker.SelectedDateProperty, new Binding("Date")
  {
    Source = _event,
    Mode = BindingMode.TwoWay
  });

  // bind the Title to the UI
  EventTitle.SetBinding(TextBox.TextProperty, new Binding("Title")
  {
    Source = _event,
    Mode = BindingMode.TwoWay
  });
}

Most applications create bindings within the XAML markup ... and that is exactly what we shall do next!

Binding Markup Extension

It is quite unusual to see code which creates bindings in C# (code behind), this is because there is a simpler, more elegant way. Firstly, we'll change the code above to remove all of the bindings, and instead set the DataContext of the page to the event that we wish to edit:

public MainPage()
{
  InitializeComponent();

  // create a model object
  _event = new EventModel()
  {
    Date = new DateTime(2011, 7, 1),
    Title = "Silverlight User Group"
  };

  // bind the Date to the UI
  this.DataContext = _event;
}

Next we'll update the XAML, removing the names of the controls used to edit this event (we only named them so that they were accessible form code-behind), and adding bindings as follows:

<Grid x:Name="LayoutRoot" Background="White">
  ...

  <TextBlock Text="Name:"
              Grid.Row="1"/>
  <TextBox Text="{Binding Path=Title, Mode=TwoWay}"
           Grid.Row="1" Grid.Column="1"/>

  <TextBlock Text="Date:"
              Grid.Row="2"/>
  <sdk:DatePicker SelectedDate="{Binding Path=Date, Mode=TwoWay}"
                  Grid.Row="2" Grid.Column="1"/>
</Grid>

Compiling and running this modified version of our code yields exactly the same result. The UI displays the initial values of the event, and the bindings take care of updating the UI if the model changes and updating the model based on user interactions with the controls. So let's take a closer look at how these binding work.

Within XAML, any attribute value that is surrounded in curly braces {...} is a markup extension. Whilst most of your XAML markup is simply used to construct controls and panels in order to assemble the visual tree of your application, markup extension inject extra functionality into the XAML parser. The Binding Markup Extension constructs a binding and associates it with the UIElement or FrameworkElement that the property belongs to.

As you might recall from the previous blog post, all bindings have a source object, source property, target object and target property:

How do these map to our XAML binding? Let's look at the various components of our markup:

The target object is the object that owns the property which we are binding to, i.e. the UI control rendering our data. The target property is the property that has been set via the markup extension, and the source property is the path of the binding.

However, there is something missing? Where is the source object? In our example above, where is the object that owns this Title property we are binding to the UI? If you recall earlier we set the DataContext property of our MainPage to the event class which is being edited ... this is our source object. The DataContext is a rather special property for a couple of reasons:

  1. Inheritence - The DataContext value is inherited down the visual tree from one control to the next. Even though we set the DataContext of MainPage to our model object, if you set a breakpoint in the code and inspect the DataContext of the above TextBox you will also find that it is set to the same event object.
  2. Default source - Any bindings that are defined without the source object being specified (via the Binding.Source property), will take the DataContext of the target object as the source. In the case illustrated above this is the DataContext of the TextBox, which has been inherited from MainPage.

These two properties of DataContext and the binding framework result in a concise and elegant way to wire up your applications user-interface.

The Property Path Syntax

We'll explore a few other features of the binding framework via a more complex example, this time we have a model object, PersonModel, that has a relationship to another model object, AddressModel. A summary of these classes is show below (INotifyPropertyChanged implementation omitted for clarity):

public class PersonModel : INotifyPropertyChanged
{
  public string Forename { ...  }

  public string Surname { ...  }

  public AddressModel WorkAddress { ...  }
}

public class AddressModel : INotifyPropertyChanged
{
  public string Street { ...  }
 
  public string City { ...  }
}

Again, we create an instance of this class and set it as the DataContext of our user control:

_model = new PersonModel()
{
  Surname = "Eberhardt",
  Forename = "Colin",
  WorkAddress = new AddressModel()
  {
    Street = "6 Charlotte Square",
    City = "Newcastle"
  }
};
 

// bind the Date to the UI
this.DataContext = _model;

(Aren't C# object initializers just great!)

Binding the Surname and Forename to the UI is straightforward, but how about the WorkAddress? The Path property of a Binding supports a special property path-syntax which has a dot notation for navigating relationships. We can bind our UI to the various properties of the Address as follows:

<TextBlock Text="Street:"  ... />
<TextBox Text="{Binding Path=WorkAddress.Street, Mode=TwoWay}" ... />

<TextBlock Text="City:" ... />
<TextBox Text="{Binding Path=WorkAddress.City, Mode=TwoWay}" ... />

Which yields the following UI:

Now because Address implements INotifyPropertyChanged, if we change the Street or City of the object bound to the UI, the binding framework will take care of pushing this change to the binding targets (i.e. the TextBox instances). But, what if we replace the Address instance entirely? For example ...

private void Button_Click(object sender, RoutedEventArgs e)
{
  _model.WorkAddress = new AddressModel()
    {
      Street = "62 Arcacia Ave.",
      City = "Bananaville"
    };
}

Again, the binding framework updates the UI. The binding framework not only detects changes in the source property, it is able to detect changes at any point in the chain of property relationships from the DataContext. That's pretty smart!

The property-path syntax supports binding to arrays and dictionaries via indexers, for example:

{Binding Path=ArrayOfThings[0]}

and

{Binding Path=DictionaryOfStuff["foo"]}

I'm not going to give examples of all the various bindings that are possible, MSDN has a good reference for these.

Creating Binding 'Islands'

In the previous example we had two bindings that navigated the relationship from Person to Address to bind to properties of the Address object. Instead of repeatedly navigating the same relationship, we can bind a 'region' of our UI to the WorkAddress property.

If you recall, the DataContext is inherited throughout the visual tree and is the default source for bindings. If we change the DataContext of a common root element of our various Address bindings to the Person.WorkAddress, we can simplify our bindings. We could set the DataContext in code-behind, but there is a more elegant way:

<TextBlock Text="Forename:"  ... />
<TextBox Text="{Binding Path=Forename, Mode=TwoWay}"  ... />

<Border DataContext="{Binding WorkAddress}">
  <Grid util:GridUtils.RowDefinitions=",," util:GridUtils.ColumnDefinitions=",">

    <TextBlock Text="Street:"  ... />
    <TextBox Text="{Binding Path=Street, Mode=TwoWay}"  ... />

    <TextBlock Text="City:"  ... />
    <TextBox Text="{Binding Path=City, Mode=TwoWay}"  ... />
  </Grid>
</Border>

In the above XAML we are binding the DataContext of the Border element to the WorkAddress property of the Person instance (which was set as the DataContext of our UI in code-behind). As a result, all the children of the Border have the Address instance as their DataContext, so we can bind to the properties directly. This creates an 'island' within our UI that is bound to Address as shown graphically below:

Binding Shorthand and Longhand

We'll wrap up part two of the series on databinding by looking at a couple of variations in the binding syntax, the first is a 'longhand' version of the binding. Instead of using the binding markup extension, it is possible to create the binding instance in XAML, for example, the simple binding to Forename:

<TextBox Text="{Binding Path=Forename}"/>

Can be expressed as follows:

<TextBox>
  <TextBox.Text>
    <Binding Path="Forename"/>
  </TextBox.Text>
</TextBox>

This yields exactly the same result. What's the point in the longhand version? Good question, I have only used it occasionally myself, typically within multi-bindings, or where validation rules are being added.

If instead we want to make our bindings simpler, we can omit the Path and simply express the binding as follows:

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

Now there are some instances where you do not want to bind to a property of your source (i.e. DataContext), rather you want to bind to the source itself. The syntax for this is a single 'dot':

<TextBox Text="{Binding Path=.}"/>

Or, in this case, we can use the very-very short:

<TextBox Text="{Binding}"/>

We'll see some instances of when this might be useful in a future part of this blog series on binding when we look at collection-binding.

Summary

In this blog post we have looked at the binding markup extension and how this provides a concise mechanism for creating bindings. We have also seen how the DataContext, which is the default source for our bindings, plays a pivotal role in the Silverlight and WPF binding frameworks. Finally, we have seen how we can use bindings to 'switch' the DataContext in order to create binding 'islands' within out UI.

In the next instalment we'll look at some of the other ways we can specify the binding source and when you might use them, but now we'll take a well-earned break ... see you next time.

You can download the sourcecode for the examples described in this blogpost: DatabindingExamplesPartTwo.zip

Regards, Colin E.

blog comments powered by Disqus