http://msdn.microsoft.com/en-us/magazine/cc163299.aspx#S1
Data Binding in WPF
John Papa
Code download available at:DataPoints2007_12.exe(161 KB)
By now, many of you know that Windows®
Presentation Foundation (WPF) makes it easy to design robust user
interfaces. But what you probably didn't know is that it also provides
powerful data-binding capabilities. With WPF, you can perform data
manipulation using Microsoft® .NET
Framework code, XAML, or a combination of both. You can bind to
controls, public properties, XML, or objects, making data binding quick,
flexible, and easier than ever. So let's take a look at how you can get
started binding your controls to the data sources of your choice.
Data Binding Specifics
To
use WPF data binding, you must always have a target and a source. The
target of the binding can be any accessible property or element that is
derived from DependencyProperty—an example is a TextBox control's Text
property. The source of the binding can be any public property,
including properties of other controls, common language runtime (CLR)
objects, XAML elements, ADO.NET DataSets, XML Fragments, and so forth.
To help you get the binding right, WPF includes two special
providers—the XmlDataProvider and the ObjectDataProvider.
Now let's take a look at how the WPF data-binding techniques work and I'll present practical examples that illustrate their use.
Creating a Simple Binding
Let's
start with a simple example that illustrates how to bind a TextBlock's
Text property to a ListBox's selected item. The code displayed in Figure 1
shows a ListBox that has six ListBoxItems declared. The second
TextBlock in the code example has a property called Text (specified in
XAML property-element syntax with the XML child element
<TextBlock.Text>), which will contain the text for the TextBlock.
The Text property declares a binding to the ListBox's selected item with
the <Binding> tag. The ElementName attribute of the Binding tag
indicates the name of the control that the TextBlock's Text property is
bound to. The Path attribute indicates the property of the element (in
this case the ListBox) that we will be binding to. The result of this
code is that when a color is selected from the ListBox, the name of that
color is shown in the TextBlock.
Figure 1 Basic but Verbose Binding to a Control
<StackPanel> <TextBlock Width="248" Height="24" Text="Colors:" TextWrapping="Wrap"/> <ListBox x:Name="lbColor" Width="248" Height="56"> <ListBoxItem Content="Blue"/> <ListBoxItem Content="Green"/> <ListBoxItem Content="Yellow"/> <ListBoxItem Content="Red"/> <ListBoxItem Content="Purple"/> <ListBoxItem Content="Orange"/> </ListBox> <TextBlock Width="248" Height="24" Text="You selected color:" /> <TextBlock Width="248" Height="24"> <TextBlock.Text> <Binding ElementName="lbColor" Path="SelectedItem.Content"/> </TextBlock.Text> </TextBlock> </StackPanel>
The code listed in Figure 1 can be modified slightly to use a shorthand syntax for data binding. For example, let's replace the TextBlock's <Binding> tag with the following code snippet:
<TextBlock Width="248" Height="24" Text="{Binding ElementName=lbColor, Path=SelectedItem.Content}" />
This syntax, called the attribute syntax, condenses the data binding code inside of the Text attribute of the TextBlock. Basically, the Binding tag gets pulled inside of the curly braces along with its attributes.
Binding Modes
I can take the previous example further, binding the background color of the TextBlock to the color that is selected in the ListBox. The following code adds a Background property to the TextBlock and uses the attribute binding syntax to bind to the value of the selected item in the ListBox:
<TextBlock Width="248" Height="24" Text="{Binding ElementName=lbColor, Path=SelectedItem.Content, Mode=OneWay}" x:Name="tbSelectedColor" Background="{Binding ElementName=lbColor, Path=SelectedItem.Content, Mode=OneWay}"/>
When the user selects a color in the ListBox, the name of that color will appear in the TextBlock and the background color of the TextBlock will change to the selected color (see Figure 2).
Figure 2 Binding a Source to Two Targets
Notice the statement in the previous example that sets the Mode attribute to OneWay. The Mode attribute defines the binding mode that will determine how data flows between the source and the target. In addition to OneWay, there are three other binding modes available: OneTime, OneWayToSource, and TwoWay,
When using OneWay binding, as shown in the preceding code snippet, the data flows from the source to the target each time a change is made on the source. And while I explicitly specified this binding mode in my example, OneWay binding is the default binding mode for the TextBlock's Text property and does not need to be specified. Like OneWay binding, OneTime binding sends data from the source to the target; however, it does this only when the application is started or when the DataContext changes and, as a result, does not listen for change notifications in the source. Unlike OneWay and OneTime binding, OneWayToSource binding sends data from the target to the source. Finally, TwoWay binding sends the source data to the target, and if there are changes in the target property's value, those will be sent back to the source.
In the previous example, I used OneWay binding because I wanted the source (the selected ListBoxItem) to be sent to the TextBlock whenever a change is made in the ListBox selection. I do not want changes from the TextBlock to go back to the ListBox. Of course, there is no way for a user to edit a TextBlock. If I want to explore TwoWay binding, I can add a TextBox to this code, bind its text and background color to the ListBox, and set the Mode to TwoWay. When a user selects a color in the ListBox, the color is shown in the TextBox and its background color changes. When that user types in a color (such as Cyan) in the TextBox, the name of the color is updated in the ListBox (target to source) and in turn, since the ListBox was updated, the new value is sent to all elements that are bound to the ListBox's SelectedItem property. This means that the TextBlock will also have its color updated and its text value set to the new color (see Figure 3).
Figure 3 TwoWay Binding in Action
Here's the code I used to bind the TextBlock (OneWay) and the TextBox (TwoWay) to the ListBox:
<TextBlock Width="248" Height="24" Text="{Binding ElementName=lbColor, Path=SelectedItem.Content, Mode=OneWay}" x:Name="tbSelectedColor" Background="{Binding ElementName=lbColor, Path=SelectedItem.Content, Mode=OneWay}"/> <TextBox Width="248" Height="24" Text="{Binding ElementName=lbColor, Path=SelectedItem.Content, Mode=TwoWay}" x:Name="txtSelectedColor" Background="{Binding ElementName=lbColor, Path=SelectedItem.Content, Mode=OneWay}"/>
If I change the TwoWay mode back to OneWay, the user will be able to edit the color in the TextBox without causing the changed value to be sent back to the ListBox.
Selecting the appropriate binding mode is important. I often employ OneWay when I want to display read-only data to a user. I use TwoWay binding when I want the user to be able to change the data in the control and have that change reflected in the data source (a DataSet, object, XML, or another bound control). I find OneWayToSource to be a good choice when I want to allow a user to change the data source without having the data source bind its data back to the target. I have used OneTime binding when I was tasked with showing, in a read-only control, the state of the data as it was when the screen loaded. Using OneTime binding, a series of read-only controls was bound to the data and, as the user interacted with the form and the data source's values were changed, the bound controls remained unchanged. This provided a way for the users to compare the changes that were made. Additionally, OneTime binding is a good choice when your source doesn't implement INotifyPropertyChanged.
A Time to Bind
In the previous example, the TextBox allows TwoWay binding to the selected ListBoxItem in the ListBox. This flow of data from the TextBox back to the ListBox happens when the TextBox loses focus. To change the event that causes the data to be sent back to the source, you can specify a value for UpdateSourceTrigger, which is the binding property that defines when the source should be updated. There are three values that can be set for the UpdateSourceTrigger: Explicit, LostFocus, and PropertyChanged.
When you set the UpdateSourceTrigger to Explicit, the source will not be updated unless the BindingExpression.UpdateSource method is called from code. The LostFocus setting (the default value for the TextBox control) indicates that the source will be updated when the target control loses focus. The PropertyChanged value indicates that the target will update the source every time the target control's bound property changes. This setting is useful when you want to dictate when the binding will occur.
Binding to XML
Binding to data sources such as XML and objects is also handy. Figure 4 shows a sample of an XmlDataProvider that contains an embedded list of colors that will be used as a data source. The XmlDataProvider can be used to bind to an XML document or fragment that is either embedded in the XmlDataProvider tag or is in a file referred to in an external location.
Figure 4 XmlDataProvider
<StackPanel> <StackPanel.Resources> <XmlDataProvider x:Key="MoreColors" XPath="/colors"> <x:XData> <colors > <color name="pink"/> <color name="white"/> <color name="black"/> <color name="cyan"/> <color name="gray"/> <color name="magenta"/> </colors> </x:XData> </XmlDataProvider>
Embedded XML content must be placed within a <x:XData> tag inside of an XmlDataProvider, as shown in Figure 4. The XmlDataProvider must be given an x:Key value so that it can be referred to by data-binding targets. Notice that the XPath attribute is set to "/colors". This attribute defines the level of the XML content that will be used as the data source. This becomes very useful when binding to a large XML structure that may be contained in a file or database and the data you want to bind to is not the root element.
An XmlDataProvider is a resource that can be placed inside of a context-specific resource. As Figure 4 shows, the XmlDataProvider is defined as a resource within the context of the StackPanel. This means that the XmlDataProvider will be available to all content inside of that StackPanel. Setting the context of a resource helps limit the exposure of a data source to the appropriate areas. This enables you to create well-defined, self-contained regions of both controls and supporting resources within your page, thus improving readability.
The syntax for binding to a resource is slightly different than it is for binding to an element. When binding to a control, you set the ElementName and the Path properties of the Binding. However, when you bind to a resource you set the Source and, since we are binding to an XmlDataProvider, you set the XPath property of the Binding as well. For example, the following code will bind the ListBox's items to the MoreColors resource. The Source property is set to a resource and it is specified as a StaticResource named MoreColors. The XPath property indicates that the items will be bound to the <color> element's name attribute within the XML data source:
<ListBox x:Name="lbColor" Width="248" Height="56" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Source={StaticResource MoreColors}, XPath=color/@name}"> </ListBox>
I specified StaticResource in this case because the XML will not change. If changes occur in the data source, they will not be sent to the target. The DynamicResource setting indicates the opposite—changes will be sent. This is useful when referencing system themes, languages in globalization, or fonts. A DynamicResource will allow these types of settings to be propagated throughout the UI elements that are bound to them dynamically.
The XmlDataProvider can also point to an external source for the XML content. For my example, I have a file named colors.xml that contains the list of colors I want my ListBox to be bound to. I can simply add a second XmlDataProvider resource to the StackPanel and direct it to the XML file. Notice I set the Source attribute to the name of the XML file and the x:Key to Colors:
<XmlDataProvider x:Key="Colors" Source="Colors.xml" XPath="/colors"/>
Both XmlDataProviders exist as resources within the same StackPanel. I can tell the ListBox to bind itself to this new resource by changing the name that the StaticResource is set to:
<ListBox x:Name="lbColor" Width="248" Height="56" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Source={StaticResource Colors}, XPath=color/@name}"> </ListBox>
Object Binding and DataTemplates
While the XmlDataProvider works great for XML, when you want to bind to an object or a list of objects, you can create an ObjectDataProvider as a resource. The ObjectDataProvider's ObjectType designates the object that will provide the data‑binding source while the MethodName indicates the method that will be invoked to get the data. For example, assuming I have a class called PersonService that has a method called GetPersonList that returns a List<Person>, the ObjectDataProvider would look like this:
<StackPanel.Resources> <ObjectDataProvider x:Key="persons" ObjectType="{x:Type svc:PersonService}" MethodName="GetPersonList"></ObjectDataProvider> </StackPanel.Resources>
If you want a more complete look, the PersonService and Person classes, as well as all other sample code, are contained in the code that accompanies this column.
There are a handful of other properties that are available on the ObjectDataProvider. The ConstructionParameters property allows you to pass parameters to the constructor of the class being invoked. You can also specify parameters using the MethodParameters property, and you can use the ObjectInstance property to specify an existing instance of an object as the source.
If you want the data to be retrieved asynchronously, you can set the IsAsynchronous property of the ObjectDataProvider to true. Then the user will be able to interact with the screen while waiting for the data to populate in the target control that is bound to the ObjectDataProvider's source.
When adding an ObjectDataProvider, you have to qualify the namespace of the data-source class. In this case, I have to add an xmlns attribute to the <Window> tag so that the svc shortcut is qualified and indicates the proper namespace:
xmlns:svc="clr-namespace:DataBindingWPF"
Now that the data source is defined via the ObjectDataProvider, I want to bind items in a ListBox control to this data. I want to display two lines of text in each ListBoxItem. The first line will display the FullName property of the Person instance in bold and the second line will show the Title and City of the instance. In XAML, this is quite simple using DataTemplates, which allow you to define a data visualization strategy that can be reused.
Figure 5 shows the completed XAML, which has a DataTemplate defined to display the Person information in the layout I designated. I set the DataType property of the DataTemplate to indicate that the DataTemplate will be referring to the Person class type. I do not specify the actual binding in the DataTemplate, as I will do this in the ListBox control. By omitting the Binding Source, the binding will be made to the current DataContext in scope.
In Figure 5, I set the ListBox's ItemsSource property to bind to the persons resource so that I could bind the data to the ListBox but not format it. The data is correctly displayed by setting the ItemTemplate property to the personLayout resource, which is the DataTemplate's key name. The ultimate result is a screen that looks like Figure 6.
Figure 5 Object Binding
<Window x:Class="DataBindingWPF.ObjectBinding" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:svc="clr-namespace:DataBindingWPF" Title="DataBindingWPF" Height="300" Width="300"> <StackPanel> <StackPanel.Resources> <ObjectDataProvider x:Key="persons" ObjectType="{x:Type svc:PersonService}" MethodName="GetPersonList" ></ObjectDataProvider> <DataTemplate x:Key="personLayout" DataType="Person"> <StackPanel Orientation="Vertical"> <TextBlock Text="{Binding Path=FullName}" FontWeight="Bold" Foreground="Blue"> </TextBlock> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=Title}"></TextBlock> <TextBlock Text=", "></TextBlock> <TextBlock Text="{Binding Path=City}"></TextBlock> </StackPanel> </StackPanel> </DataTemplate> </StackPanel.Resources> <TextBlock></TextBlock> <ListBox x:Name="lbPersons" ItemsSource="{Binding Source={StaticResource persons}}" ItemTemplate="{DynamicResource personLayout}" IsSynchronizedWithCurrentItem="True"/> </StackPanel> </Window>
Figure 6 Using a DataTemplate
Sorting the Data
If you want to sort your data in a specific way, you can bind to a CollectionViewSource instead of directly to the ObjectDataProvider. The CollectionViewSource then becomes the data source and serves as an intermediary that intercepts the data from the ObjectDataProvider; provides sorting, grouping, and filtering functionality; and then propagates it to the target.
The CollectionViewSource shown next has its Source attribute set to the resource name of the ObjectDataProvider (persons). I then defined a sort order for the data by indicating the properties to sort by and their direction:
<CollectionViewSource x:Key="personView" Source="{Binding Source={StaticResource persons}}"> <CollectionViewSource.SortDescriptions> <ComponentModel:SortDescription PropertyName="City" Direction="Ascending" /> <ComponentModel:SortDescription PropertyName="FullName" Direction="Descending" /> </CollectionViewSource.SortDescriptions> </CollectionViewSource>
The DataContext is used to bind all controls within a container control to a data source. This is very useful when you have several controls that all use the same binding source. The code could get repetitive if you indicated the binding source for every control. Instead, you can set the DataContext for the container of the controls to the binding source and simply omit the Source attribute from the contained controls. For example, here is a series of TextBlocks bound explicitly to the same binding source:
<StackPanel> <TextBlock Text="{Binding Source={StaticResource personView}, Path=FullName}"></TextBlock> <TextBlock Text="{Binding Source={StaticResource personView}, Path=Title}"></TextBlock> <TextBlock Text="{Binding Source={StaticResource personView}, Path=City}"></TextBlock> </StackPanel>
Here are the same three TextBoxes bound to the DataContext, which refers back to the controls' StackPanel container:
<StackPanel DataContext="{Binding Source={StaticResource personView}}" > <TextBlock Text="{Binding Path=FullName}"></TextBlock> <TextBlock Text="{Binding Path=Title}"></TextBlock> <TextBlock Text="{Binding Path=City}"></TextBlock> </StackPanel>
If the container does not define a DataContext, then it will continue to look at the next outer nested container until it finds the current DataContext.
Forging Ahead
WPF data binding offers a great deal of flexibility and control over the type of data that can be bound to, and how it can be controlled and displayed. With so much power and so many choices, I'm sure you'll want to get your hands dirty right away.
Send your questions and comments for John to mmdata@microsoft.com.