Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Guided Tour of WPF � Part 3 (Data binding)

0.00/5 (No votes)
4 Apr 2007 5  
A guided tour of the Windows Presentation Foundation, one feature at a time.

Table of contents

  • Part 1 (XAML): Learn about XAML and how it is used in WPF applications.
  • Part 2 (Layout): Learn about layout panels and how they are used to construct user interfaces.
  • Part 3 (Data binding): Learn how WPF data binding works and how it can be used.
  • Part 4 (Data templates and triggers): Learn how data templates and triggers work and how they can be used.
  • Part 5 (Styles): Learn about how UIs can be styled in WPF.

Introduction

This is the third article in an introductory series about the Windows Presentation Foundation. In the previous article we examined layout panels and how they are used to create WPF user interfaces. In this article we will explore the world of data binding, and how it is put to use in the WPF Horse Race demo application (which is available for download at the top of the first article in this series).

For a comprehensive review of WPF data binding be sure to refer to the links listed in the External links section at the bottom of the page. This article covers the bare essentials of WPF data binding, and demonstrates various ways in which the WPF Horse Race application uses data binding.

Background

Data binding in the user interface layer is nothing new. It has been around for quite some time, in various UI platforms, both for desktop and Web applications. The basic idea is that you "bind" the visual elements (controls) in a user interface to the data objects they are meant to display. The binding infrastructure then manages the data interactions from then on, so that modifications to the UI controls are reflected in the data objects, and vice versa. The major benefit of using data binding is that it reduces the amount of code the application developer needs to write.

The architects of some earlier UI platforms have done a good job integrating data binding with the rest of their framework. WPF's architects have done an amazing job integrating data binding with all aspects of their framework. Data binding in WPF is ubiquitous and seamless. It is so powerful and flexible that it literally forces you to change the way you think about designing and developing user interfaces.

With one simple API you can bind to domain/business objects, XML data, visual elements, ADO.NET data containers, collections, and basically anything else you can think of. You can use value converters to execute arbitrary data manipulation operations when bound values are passed back and forth. You can perform data validation by creating custom validation rules and applying them to a binding. The list goes on. Data binding in WPF is really a huge step forward.

Dependency properties

Before diving into the guts and glory of WPF data binding, it is necessary to take a detour and briefly discuss another fundamental feature of WPF which makes it all possible. In WPF a property can only be bound if it is a dependency property.

Dependency properties are like normal .NET properties on steroids. There is a whole dependency property infrastructure in place, which provides an array of features for application developers to make their lives easier. For our purposes in this article it is important to know the following things about dependency properties:

  1. They can determine their value by retrieving it from a Binding object (i.e. they can be bound).
  2. They can participate in property value inheritance, meaning that if a dependency property on an element does not have a value it will use the value of that property on an ancestor element (in the logical tree). This is somewhat analogous to ambient properties in the Windows Forms world.
  3. They can be set in XAML, just like a normal property.

The reasons why those aspects of dependency properties are important will become clear later on, as we examine how data binding uses them. For more information about dependency properties, refer to the Dependency properties sub-section in the External links section at the bottom of this page.

DataContext

User interface elements in WPF have a DataContext dependency property. That property has the aforementioned "value inheritance" feature enabled, so if you set the DataContext on an element to a Foo object, the DataContext property on all of its logical descendant elements will reference that Foo object too. This means that all data bindings contained within that root element's element tree will automatically bind against the Foo object, unless explicitly told to bind against something else. This feature is extremely useful, as we will soon see.

The Binding class

Data binding in WPF revolves around the Binding class. Binding is the sun of the data binding solar system, so to speak.

That class has a fairly simple and intuitive API, but it is very powerful. The WPF Horse Race demo application does not nearly use all of the features of the Binding class, but it does make use of some common ones. Let's take a look at the Binding members we will see in action later on in this article.

  • Source - references a Binding's data source. By default this object references the element's DataContext value, which might be inherited from an ancestor element's DataContext, as discussed in the previous section. If you set this property to a non-null value, then the data binding operation will treat that value as the place where data is pushed to and pulled from.
  • Path - is used to indicate from which property on the source object to get and set the bound data value. It is a property of type PropertyPath, which allows it to support a complex range of path expressions.
  • ElementName - can be used as an alternative to the Source property described earlier. It allows you to specify the name of an element to use as a data source. This can be useful when binding a property on one element to a property on another element, particularly when the binding is declared in XAML.
  • Converter - of type IValueConverter. You can set this property to an instance of a class which implements that interface to intercept any movement of data from the binding source to the binding target, or vice versa. Value converters are very convenient and are used quite often.

How the WPF Horse Race uses data binding

Now that we have a general idea of how data binding is structured, it's about time to see how the WPF Horse Race demo application uses it. We won't examine each place that data binding is used in the application; some of them are better saved for a later article in this series. All of the data binding logic in the application is expressed in XAML, but keep in mind that it is entirely possible to do all of this in the code-behind as well.

Displaying a racehorse's name

Open the RaceHorseDataTemplate.xaml file, which contains the DataTemplate for the RaceHorse class (if you don't know what a DataTemplate is yet, don't worry, the next article in this series covers that topic). In that file there is a TextBlock named 'horseName' which is bound to the Name property of a RaceHorse object. Here's an abridged version of the XAML I'm referring to:

<TextBlock x:Name="horseName" Text="{Binding Name}" />

When a RaceHorse named 'Sweet Fate' is displayed in the UI, that TextBlock displays its name as seen below:

Displaying a horse's name

Notice that the binding statement in the XAML snippet above does not explicitly mention that it is setting the Binding's Path property. Markup extensions, such as Binding, can have one property which acts as the "default" property when being set in XAML. Binding's default property is Path. If you are not in favor of using implicit notation like that, the XAML seen below is equivalent to the previous snippet:

<TextBlock x:Name="horseName" Text="{Binding Path=Name}" />

Rotating the race track

In the main Window's XAML file (Window1.xaml) there is a Slider control which affects the rotation angle of the "race track" ItemsControl. The ItemsControl has its LayoutTransform property set to a RotateTransform resource, so the Slider must bind to that RotateTransform in order to affect the rotation of the ItemsControl. The result of this binding is that when the user moves the thumb in the Slider, the "race track" rotates, like this:

Rotating the race track

The following XAML is a skeletal outline of the Window, showing only the markup relevant to this particular binding:

<Window>
  <Grid>
    <Grid.RowDefinitions>
      <!-- The top row is for the race track. -->
      <RowDefinition Height="*" />
      <!-- The bottom row is for the command strip. -->
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <Grid.Resources>
      <RotateTransform x:Key="trans" Angle="0" />
    </Grid.Resources>

    <!-- The 'Race Track' area. -->
    <ItemsControl LayoutTransform="{StaticResource trans}" />

    <!-- The 'Command Strip' area -->
    <Border Grid.Row="1">
      <Slider Value="{Binding Source={StaticResource trans}, Path=Angle}" />
    </Border>
  </Grid>
</Window>

Displaying the angle of rotation

Directly beneath the Slider in the main Window's XAML is a TextBlock whose Text property is bound to the Slider's Value property. The purpose of this TextBlock is to show the angle of rotation applied to the race track. Since this should display a user-friendly number, and the Slider's Value property is a double, a value converter is used to remove all of the decimal places (basically it casts the double to an int). Here's how that works:

<StackPanel>
  <StackPanel.Resources>
    <local:DoubleToIntegerConverter x:Key="conv" />
  </StackPanel.Resources>
  ...
  <Slider x:Name="rotationSlider" />

  <TextBlock Text="{Binding
    ElementName=rotationSlider,
    Path=Value,
    Converter={StaticResource conv}}"
    />
  ...
</StackPanel>

That binding uses the ElementName property to refer to the Slider element. It also uses a custom value converter to convert the Value property of the Slider to an integer. Below is the code for the value converter:

public class DoubleToIntegerConverter : IValueConverter
{
 public object Convert(
  object value, Type targetType,
  object parameter, CultureInfo culture )
 {
  return (int)(double)value;
 }

 public object ConvertBack(
  object value, Type targetType,
  object parameter, CultureInfo culture )
 {
  throw new NotSupportedException( "Cannot convert back" );
 }
}

Calculating the width of a racehorse's progress indicator

As a racehorse "runs the race" it is followed by a green progress indicator. That indicator's width represents what percent of the race the horse has completed as seen below:

The progress indicator in action

Determining the width of the progress indicator requires two pieces of information: the percent of the race completed by the racehorse, and the total distance which the racehorse must travel to reach the finish line. Based on what we have seen of data binding in WPF so far, this seems like a problem. How can you bind the width of an element to a value whose calculation requires two numbers? That's where the MultiBinding and IMultiValueConverter types come into play.

Here's an approximation of how that works:

<Border x:Name="racePit">
  <Grid>
    <StackPanel>
      <StackPanel.Resources>
        <local:RaceHorseProgressIndicatorWidthConverter x:Key="WidthConv" />
      </StackPanel.Resources>
      <!-- This Rectangle "follows" a horse as it runs the race. -->
      <Rectangle x:Name="progressIndicator"
        Fill="{StaticResource RaceInProgressBrush}"
        >
        <!-- The progress indicator width is calculated by an instance
             of the RaceHorseProgressIndicatorWidthConverter class. -->
        <Rectangle.Width>
          <MultiBinding Converter="{StaticResource WidthConv}">
            <Binding Path="PercentComplete" />
            <Binding ElementName="racePit" Path="ActualWidth" />
          </MultiBinding>
        </Rectangle.Width>
      </Rectangle>
    </StackPanel>
  </Grid>
</Border>

The multi-value converter used to calculate the progress indicator's width is seen below:

public class RaceHorseProgressIndicatorWidthConverter : IMultiValueConverter
{
 public object Convert(
  object[] values, Type targetType,
  object parameter, CultureInfo culture )
 {
  int percentComplete = (int)values[0];
  double availableWidth = (double)values[1];
  return availableWidth * (percentComplete / 100.0);
 }
 public object[] ConvertBack(
  object value, Type[] targetTypes,
  object parameter, CultureInfo culture )
 {
  throw new NotSupportedException( "Cannot convert back" );
 }
}

External links

Dependency properties

Data binding

History

  • April 3, 2007 � Created the article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here