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:
- They can determine their value by retrieving it from a
Binding
object (i.e. they can be bound).
- 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.
- 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:
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:
The following XAML is a skeletal outline of the Window, showing only the markup relevant to this particular binding:
<Window>
<Grid>
<Grid.RowDefinitions>
-->
<RowDefinition Height="*" />
-->
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.Resources>
<RotateTransform x:Key="trans" Angle="0" />
</Grid.Resources>
-->
<ItemsControl LayoutTransform="{StaticResource trans}" />
-->
<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:
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>
-->
<Rectangle x:Name="progressIndicator"
Fill="{StaticResource RaceInProgressBrush}"
>
-->
<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.