Introduction
Conventionally, in Windows Forms and ASP.NET applications, data binding was primarily used for populating elements on the screen with information; there was simple data binding for displaying single values, and complex data binding for displaying and formatting a collection of data. The beauty of data binding is that you can populate the interface while writing little to no code. With data binding in WPF you can take data from almost any property of any object and bind it to almost any other dependency property of another object.
In the first part of this two part series, I am going to cover the basics of data binding in WPF. In the second part, more advanced data binding concepts will be covered such as templates and varying the way information is displayed based on characteristics of the data. For both of these parts, you will need to have a basic understanding of WPF.
Prerequisites
This article is written with the assumption that the reader has already achieved a basic understanding of the following WPF concepts:
- Understanding of the basics of WPF
- Dependency properties
- Dependency property inheritance
- Resources
- Markup extensions
Basics
In its most basic form, data binding copies a value from a source object to a property on a destination object. The source property can be anything. The destination will be a dependency property. A key difference in binding to a WPF element property as the source and binding to another object type is that the WPF object has change notification; once binding has occurred with a WPF element property as a source, when the source changes, the destination property will automatically be updated. Let’s look at a couple of very simple WPF data binding expressions.
<Window x:Class="Example00.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid >
<Grid.RowDefinitions >
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Name="mySourceElement" Grid.Row="0" >Hello World</TextBox>
<TextBlock Grid.Row="1">
<TextBlock.Text>
<Binding ElementName="mySourceElement" Path="Text" />
</TextBlock.Text>
</TextBlock>
<TextBlock Text="{Binding ElementName=mySourceElement,Path=Text }" Grid.Row="2" />
</Grid>
</Window>
Example 00
As you might have guessed, when you run this example, it will display “Hello Word.” The text is displayed three times. The first instance of the text is displayed because it is the literal value assigned to the text box. The next two instances are in textboxes, and are the result of data binding. Both of the text boxes create a binding with the textbox named “mySourceElement
” as the source and their Text
attribute as the destination. While the two text blocks bind to the same source and yield the same results, the syntax used is different. The first text block uses an expanded syntax, while the second uses a markup extension to express the same binding. Within this document, I will use the extension markup, but keep in mind that in most cases, either syntax can be used. Here is a more interesting data binding example:
Property
|
Description
|
Converter
|
Sets the converter to be used.
|
ElementName
|
The name of the element to which the binding is to be made.
|
FallbackValue
|
Sets the value to use when binding cannot return a value.
|
Mode
|
Sets the direction of the binding.
|
Path
|
The path to the element property being used as the data source.
|
RelativeSource
|
Sets the binding source by specifying an element relative to the current element.
|
Source
|
The source object to use for binding.
|
StringFormat
|
Specifies the format of the string representation of a value if the element is bound to a string property.
|
UpdateSourceTrigger
|
Sets the events on which binding will occur.
|
ValidationRules
|
A collection of the validation rules applied against a binding.
|
<Window x:Class="Example01.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Example 01" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions >
<RowDefinition Height="40px" />
<RowDefinition Height="40px" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Slider Name="fontSizeSlider" Minimum="5"
Maximum="100" Value="10" Grid.Row="0" />
<TextBox Name="SizeTextBox"
Text="{Binding ElementName=fontSizeSlider, Path=Value}" Grid.Row="1"/>
<TextBlock Text="Example 01"
FontSize="{Binding ElementName=SizeTextBox, Path=Text}" Grid.Row="2"/>
</Grid>
</Window>
Example 01
In this example, there are three elements bound together. A TextBox
’s Text
property is bound to a Slider
’s Value
property, and a TextBlock
's FontSize
property is bound to the TextBox
’s Text
property. If you run the program, you will see the effects of this chained relationship. Changing the slider results in the number displayed in the textbox and the size of the font being adjusted appropriately. WPF is able to apply changes as the slider is changed because the element properties implement the INotifyPropertyChanged
interface. As its name suggests, when the property value changes, WPF is made aware and knows to reapply the values to the destination properties.
Also, note that if you click in the text box and manually change its value, the slider and the text block will both update as soon as you tab out of the text box. The data appears to flow in both directions in this example (from the slider to the textbox, and from the textbox to the slider). This behavior of data binding is controlled through the binding mode.
Binding Modes
The binding mode sets in which direction data will flow. Data usually flows from the source to the destination. There are five data binding modes.
Mode |
Description |
|
Default |
|
|
OneWayToSource |
A reverse version of OneWay . |
|
OneWay |
From source to destination. |
|
OneTime |
The property is initially set, but updates to the source are not copied to the destination. |
|
TwoWay |
Changes to source and destination are copied to each other. |
|
The OneWayToSource
mode appears redundant at first. Since it reverses the direction in which data is copied, at first glance, one might think that the same functionality can be achieved by reversing the element to which the binding expression is attached. Since the destination element must be a dependency property, that’s not always an option. When we need to set a non-dependency property with the value of a dependency property, you will need to use the OneWayToSource
mode.
Controlling When Binding Occurs
Binding that occurs in a forward direction (from source to destination) occurs immediately. When you change the slider, the numeric value in the text box, or the combo box value, the changes occur to the text block immediately. But, you may have noticed that when you enter a numeric value into the text box, the slider does not update until the text box loses focus. Binding that occurs in the reverse direction doesn’t occur immediately, by default. The UpdateSourceTrigger
property can be used to set this behavior.
Name
|
Description
|
Default
|
For most properties, the behavior is the same as PropertyChanged . For TextBox.Text , this is the same as LostFocus . The default behavior is controlled by metadata on the property.
|
Explicit
|
Binding does not occur until UpdateSource() is called.
|
LostFocus
|
Source is updated when the target loses focus.
|
PropertyChanged
|
Binding occurs immediately.
|
Binding to Public Properties on Objects that are not Elements
So far, we have looked at binding to WPF elements. When binding to a property that isn’t an element, the ElementName
property of the binding expression must be set to one of the following values instead.
Name
|
Description
|
DataContext
|
*WPF will search up the element tree until it encounters a DataContext object if a Source or RelativeSource is not used. Once it finds a non-null DataContext , that object is used for binding. Useful for binding several properties to the same object.
|
RelativeSource
|
Used to identify the source object relative to the current element. Useful for control templates and data templates. (Data templates will be covered in part 2 of this article.)
|
Source
|
The reference in the binding expression is to the object supplying the data.
|
* - Actually no runtime searching occurs. The DataContext
property inherits its value.
If no source is defined in a binding expression, then the source is assumed to be the data context. When several elements are being bound to the same source object, the implicit use of the data context saves a lot of typing. Instead of specifying the data sources on all of the elements that bind to the same source, we can assign the source object to the data context of some parent object.
<Window.Resources>
<local:Employee
x:Key="MyEmployee" EmployeeNumber="123" FirstName="John"
LastName="Doe" Department="Product Development" Title="QA Manager"
/>
</Window.Resources>
<Grid DataContext="{StaticResource MyEmployee}">
<TextBox Text="{Binding Path=EmployeeNumber}"></TextBox>
<TextBox Text="{Binding Path=FirstName}"></TextBox>
<TextBox Text="{Binding Path=LastName}" />
<TextBox Text="{Binding Path=Title}"></TextBox>
<TextBox Text="{Binding Path=Department}" />
</Grid>
Example 03
Binding to Collections
Dependency properties support single-value binding. For binding to a collection of objects, your destination object must derive from ItemsControl
. There are three properties in the ItemsControl
class that are important for data binding.
Name
|
Description
|
DisplayMemberPath
|
Identifies the property on the source objects to be displayed. If not specified, then WPF will call the ToString method on the class. Unless ToString has been overridden, it will only display the class of the object.
|
ItemsSource
|
Identifies the collection that has the objects to be displayed.
|
ItemsTemplate
|
Accepts a template that can be used to customize how the objects are displayed. Templates are covered in part 2 of this article.
|
You can bind to any collection that implements the IEnumerable
interface. The .NET Collection classes and Array classes implement this interface. With IEnumerable
, you will be able to perform read-only binding. To be able to edit data, other requirements must be met.
For this example, the code is binding to a collection of Employee
objects. A List
control (which inherits from ItemsControl
) displays a list of the employees’ names. Selecting one of the employees will cause their information to be displayed in the details area beside the list. To handle the display of detailed information, we will take advantage of the DataContext
object that was mentioned in the previous section. All of the fields in the details area will be bound to the same object (but different property). All of the details fields are within the same grid control. The data context on the grid control is bound to the selected item in the employee list.
<TextBox Text="{Binding Path=FirstName}" />
<TextBox Text="{Binding Path=LastName}" />
<TextBox Text="{Binding Path=Title}" />
<TextBox Text="{Binding Path=Department}" />
Example 04
When the selected item in the list is changed, the data context is automatically updated. When the DataContext
is updated, the fields in the details area that are bound to the DataContext
are also updated.
If an element were programmatically inserted or removed from the collection after it is bound, the interface won’t update. To make the list automatically respond to insertions and deletions, the interface would need to be bound to a list that implements the INotifyCollectionChanged
interface. The WPF ObservableCollection
class implements INotifyCollectionChanged
. For our example, changing the data collection from a List<Employee>
to an ObservableCollection<Employee>
is the only change needed to make sure the interface updates when employees are inserted or removed from the list. Example 5 uses an ObservableCollection
instead of a generic List
, and has a form for entering information for a new employee. When a new employee is added to the list, the ListBox
automatically updates to show the new employee.
Binding to DataTables
Binding to tables is similar to binding to a list. In WPF, you cannot bind directly to a DataTable
, but instead, must bind to a DataView
(this should not be a big deal since all tables have a view in the DefaultView
property). This restriction was also present in Windows Forms, but invisible to the developer. Since DataTable
s implement the IBindingList
interface, WPF is able to detect when a new row is added. When deleting an item, be sure not to implement the delete functionality by removing a row from the dataset. Instead, mark a row as deleted by calling Row.Delete()
(which will cause it not to be returned by the view).
Value Conversion
Sometimes, you will need to make a change to data that is being bound so that it is more appropriate for being displayed on the screen. For example, if your source data has a property that contains a path to an image, you would probably want to display the image instead of the image’s path. Value converters are responsible for performing this task.
To create a value converter, declare a class that inherits from IValueConverter
. The class will need to have the ValueConversion
attribute applied to it. The attribute takes two arguments, the type for the source data, and the type to which it is being converted. Implement the Convert
and ConvertBack
methods to perform the conversion. For the case of the image converter, we would not need to be able to convert an image back to the path from which it was loaded, so instead of implementing ConvertBack
, we can throw the NotImplemented
exception. WPF will suppress the exception (more on WPF exception suppression later).
The IValueConverter
s can be used in some rather creative ways. Let’s say you are displaying a list of sales figures, and you want to highlight figures that are above or below certain levels, by changing the color of the text. A value converter could be used here to convert the value to the appropriate color. For the following code example, value converters are used to convert Fahrenheit temperature and to change the color of the temperature text to indicate whether the temperature is hot or cold. The same converter is used on both the Celsius and Fahrenheit temperatures. To make the converters suitable for both numeric ranges, the converters expose properties that allow the limits of hot and cold to be set.
<Window.Resources>
<local:FarenheitToCelciusConverter x:Key="myTemperatureConverter" />
<local:TemperatureToColorConverter x:Key="myFRangeIndicator"
HotTemperature="80" ColdTemperature="65" />
<local:TemperatureToColorConverter x:Key="myCRangeIndicator"
HotTemperature="26.6" ColdTemperature="18.8" />
</Window.Resources>
<TextBox Name="txtFarenheit"
Foreground="{Binding Path=Text, ElementName=txtFarenheit,
Converter={StaticResource myFRangeIndicator}}"
/>
<TextBox Name="txtCelcius"
Text="{Binding UpdateSourceTrigger=PropertyChanged,
Path=Text,ElementName=txtFarenheit,
Converter={StaticResource myTemperatureConverter}}"
Foreground="{Binding Path=Text, ElementName=txtCelcius,
Converter={StaticResource myCRangeIndicator}}"
/>
Example 06
[ValueConversion(typeof(double), typeof(double))]
public class FarenheitToCelciusConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
string sourceValue = value.ToString();
double decimalValue = 0;
if (Double.TryParse(sourceValue, out decimalValue))
{
return (decimalValue - 32.0) * (5.0 / 9.0);
}
return value;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
string sourceValue = value.ToString();
double decimalValue = 0;
if (Double.TryParse(sourceValue, out decimalValue))
{
return (decimalValue * (9.0 / 5.0)) + 32.0;
}
return value;
}
#endregion
}
Example 06 Value converter code
Validation
To prevent a user from entering invalid data, your classes may contain logic to prevent invalid values from being set. But, if an exception is thrown when an invalid value is set, the user will not receive feedback explaining that a value is invalid. WPF will automatically suppress exceptions that occur during binding. To provide user feedback for validation problems, WPF has a facility named Validation Rules. One of the built-in validation rules is the ExceptionValidationRule
. The markup for applying this rule is shown below. When applied, the user will be notified of exceptions that occurred during binding.
<Binding Path="EventDate" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
Example 07
You can create your own validation rules be inheriting from the ValidationRule
class and overriding the Validate
method. If a value is valid, then a ValidationResult
object should be returned with its IsValid
property set to true
. Otherwise, this value should be set to false
, and it’s ErrorContent
property should contain details of the failure. Validation occurs before conversion, so your validation logic must be written against the unconverted value.
If you wanted a list of all of the elements that have encountered validation errors during binding, walk through the form hierarchy, testing each element’s HasError
property. If an element has an error, then you can get a collection of the errors it encountered by calling its GetErrors()
method.
In the Next Section…
For the next part in this two part article series on data binding, I’ll cover more details of validation and rules, and will introduce data templates (for customizing how information is displayed in ItemControl
s), data sources (which allow data to be pulled in from other sources), and data views (for sorting, filtering, and grouping data). Before moving on to the forthcoming second part of this article, I would suggest getting more practice with WPF data binding.