|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Services
Chapters
Feature Zones
|
Table of contents
IntroductionThis 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. BackgroundData 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 propertiesBefore 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:
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. DataContextUser interface elements in WPF have a DataContext dependency property. That property has the aforementioned "value inheritance" feature enabled, so if you set the The Binding classData binding in WPF revolves around the Binding class. 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
How the WPF Horse Race uses data bindingNow 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 nameOpen the RaceHorseDataTemplate.xaml file, which contains the <TextBlock x:Name="horseName" Text="{Binding Name}" />
When a
Notice that the binding statement in the XAML snippet above does not explicitly mention that it is setting the <TextBlock x:Name="horseName" Text="{Binding Path=Name}" />
Rotating the race trackIn the main Window's XAML file (Window1.xaml) there is a
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 rotationDirectly beneath the <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 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 indicatorAs 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>
<!-- 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 linksDependency propertiesData binding
History
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||