Chapter 5. Data Binding

WPF has an extremely rich data binding model. It revolves around the notion that you can take almost any object as your binding source and bind it to almost any target UI element. The binding source can be another UI element, a property of the same element, an XML file, a custom business object, a database, or an in-memory collection. The binding target can be a WPF property, an individual UI element, or a WPF user control or window. But the essential idea is that once a binding is established, the data in the source is automatically and dynamically propagated to the binding target, and vice versa.

How the data object is displayed visually is controlled primarily by data templates, data converters, and data triggers. These take an object as the binding source and translate it for display into a visual structure of UI elements. It is through this mechanism that you can, for example, take a collection of custom business objects, representing products in a product catalog, and display them in a rich and visually compelling manner. Any object can be converted via the data binding system into the UI elements you specify in templates and converters and can adapt and change its display based on your triggers and data template selectors.

Once you become familiar with the data binding system in WPF, you will find it an immensely productive, simple, and effective approach to rich GUI development. The amount of custom application logic you find yourself writing in the code-behind files will dwindle to nonexistence. Before long, you will be enthusiastically and whole-heartedly embracing the wonders of true object-orientated GUI development and wondering how you ever managed without it! This chapter aims to get you up to speed as quickly as possible.

The recipes in this chapter describe how to:

Bind to a Property of a UI Element

Problem

You need to bind a property of a UI element to a property of another UI element. For example, you need to bind the Text property of a System.Windows.Controls.TextBlock control to the Value property of a System.Windows.Controls.Slider control so that the text is automatically and dynamically updated when the slider is changed.

Solution

Use the System.Windows.Data.Binding markup extension, and specify the ElementName and Path attributes.

How It Works

The Binding class creates a relationship between two properties: a binding source and a binding target. In this case, the target is the property of the element with the value you want to set. The source is the property of the element you want to get the value from. The target property must be a System.Windows.DependencyProperty, is designed to support data binding.

In the XAML for the property you want to set, declare a Binding statement inside curly braces. Set the ElementName attribute to the name of the element to use as the binding source object. Set the Path attribute to the property on the source where the data should come from.

The Code

The following example demonstrates a window containing a Slider control and a TextBlock control. The XAML statement for the Text property of the TextBlock specifies a Binding statement. This statement binds it to the Value property of the slider so that when the slider's value changes, the Text property automatically changes to reflect it.

The XAML for the window is as follows:

<Window
     x:Class="Recipe_05_01.Window1"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     Title="WPF Recipes 5_01" Height="100" Width="260">
     <StackPanel>
         <Slider Name="slider"
                 Margin="4" Interval="1"
                 TickFrequency="1"
IsSnapToTickEnabled="True"
                 Minimum="0" Maximum="100"/>
         <StackPanel Orientation="Horizontal" >
             <TextBlock Width="Auto" HorizontalAlignment="Left" Margin="4"
                        Text="The value property of the slider is:" />
             <TextBlock Width="40" HorizontalAlignment="Center" Margin="4"
                        Text="{Binding
                               ElementName=slider,
                               Path=Value}" />
        </StackPanel>
    </StackPanel>
</Window>

Figure 5-1 shows the resulting window.

Binding to a property of another element

Figure 5-1. Binding to a property of another element

Create a Two-Way Binding

Problem

You need to create a two-way binding so that when the value of either property changes, the other one automatically updates to reflect it.

Solution

Use the System.Windows.Data.Binding markup extension, and set the Mode attribute to System.Windows.Data.BindingMode.TwoWay. Use the UpdateSourceTrigger attribute to specify when the binding source should be updated.

How It Works

The data in a binding can flow from the source property to the target property, can flow from the target property to the source property, or can flow in both directions. For example, suppose the Text property of a System.Windows.Controls.TextBox control is bound to the Value property of a System.Windows.Controls.Slider control. In this case, the Text property of the TextBox control is the target of the binding, and the Value property of the Slider control is the binding source. The direction of data flow between the target and the source can be configured in a number of different ways. It could be configured such that when the Value of the Slider control changes, the Text property of the TextBox is updated. This is called a one-way binding. Alternatively, you could configure the binding so that when the Text property of the TextBox changes, the Slider control's Value is automatically updated to reflect it. This is called a one-way binding to the source. A two-way binding means that a change to either the source property or the target property automatically updates the other. This type of binding is useful for editable forms or other fully interactive UI scenarios.

It is the Mode property of a Binding object that configures its data flow. This stores an instance of the System.Windows.Data.BindingMode enumeration and can be configured with the values listed in Table 5-1.

Table 5-1. BindingMode Values for Configuring the Data Flow in a Binding

Value

Description

Default

The Binding uses the default Mode value of the binding target, which varies for each dependency property. In general, user-editable control properties, such as those of text boxes and check boxes, default to two-way bindings, whereas most other properties default to one-way bindings.

OneTime

The target property is updated when the control is first loaded or when the data context changes. This type of binding is appropriate if the data is static and won't change once it has been set.

OneWay

The target property is updated whenever the source property changes. This is appropriate if the target control is read-only, such as a System.Windows.Controls.Label or System.Windows.Controls.TextBlock. If the target property does change, the source property will not be updated.

OneWayToSource

This is the opposite of OneWay. The source property is updated when the target property changes.

TwoWay

Changes to either the target property or the source automatically update the other.

Bindings that are TwoWay or OneWayToSource listen for changes in the target property and update the source. It is the UpdateSourceTrigger property of the binding that determines when this update occurs. For example, suppose you created a TwoWay binding between the Text property of a TextBox control and the Value property of a Slider control. You could configure the binding so that the slider is updated either as soon as you type text into the TextBox or when the TextBox loses its focus. Alternatively, you could specify that the TextBox is updated only when you explicitly call the UpdateSource property of the System.Windows.Data.BindingExpression class. These options are configured by the Binding's UpdateSourceTrigger property, which stores an instance of the System.Windows.Data.UpdateSourceTrigger enumeration. Table 5-2 lists the possible values of this enumeration.

Therefore, to create a two-way binding that updates the source as soon as the target property changes, you need to specify TwoWay as the value of the Binding's Mode attribute and PropertyChanged for the UpdateSourceTrigger attribute.

Table 5-2. UpdateSourceTrigger Values for Configuring When the Binding Source Is Updated

Value

Description

Default

The Binding uses the default UpdateSourceTrigger of the binding target property. For most dependency properties, this is PropertyChanged, but for the TextBox.Text property, it is LostFocus.

Explicit

Updates the binding source only when you call the System.Windows.Data. BindingExpression.UpdateSource method.

LostFocus

Updates the binding source whenever the binding target element loses focus.

PropertyChanged

Updates the binding source immediately whenever the binding target property changes.

Note

To detect source changes in OneWay and TwoWay bindings, if the source property is not a System.Windows.DependencyProperty, it must implement System.ComponentModel.INotifyPropertyChanged to notify the target that its value has changed.

The Code

The following example demonstrates a window containing a System.Windows.Controls.Slider control and a System.Windows.Controls.TextBlock control. The XAML statement for the Text property of the TextBlock specifies a Binding statement that binds it to the Value property of the Slider control. In the binding statement, the Mode attribute is set to TwoWay, and the UpdateSourceTrigger attribute is set to PropertyChanged. This ensures that when a number from 1 to 100 is typed into the TextBox, the Slider control immediately updates its value to reflect it.

The XAML for the window is as follows:

<Window x:Class="Recipe_05_02.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     Title="WPF Recipes 5_02" Height="100" Width="260">
    <StackPanel>
        <Slider Name="slider"
                Margin="4" Interval="1"
                TickFrequency="1"
                IsSnapToTickEnabled="True"
                Minimum="0" Maximum="100"/>
        <StackPanel Orientation="Horizontal" >
            <TextBlock Width="Auto" HorizontalAlignment="Left"
                       VerticalAlignment="Center" Margin="4"
                       Text="Gets and sets the value of the slider:" />
<TextBox Width="40" HorizontalAlignment="Center" Margin="4"
                 Text="{Binding
                        ElementName=slider,
                        Path=Value,
                        Mode=TwoWay,
                        UpdateSourceTrigger=PropertyChanged}" />
        </StackPanel>
    </StackPanel>
</Window>

Figure 5-2 shows the resulting window.

Creating a two-way binding

Figure 5-2. Creating a two-way binding

Bind a Property of an Element to Itself

Problem

You need to bind one property of an element to another property of the same element.

Solution

Use the RelativeSource property of the System.Windows.Data.Binding markup extension, and specify a System.Windows.Data.RelativeSource of Self.

How It Works

The RelativeSource property of a Binding designates the binding source by specifying its relationship to the binding target. If the value of this property is set to RelativeSource.Self, then the source element is the same as the target element.

The Code

The following example demonstrates a window containing a System.Windows.Controls.Slider control. The XAML statement for the ToolTip property of the Slider control specifies a Binding statement that binds it to the Value property of itself.

The XAML for the window is as follows:

<Window x:Class="Recipe_05_03.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WPF Recipes 5_03" Height="100" Width="240">
    <Grid>
        <Slider Name="slider"
                Margin="4" Interval="1"
                TickFrequency="1"
                IsSnapToTickEnabled="True"
                Minimum="0" Maximum="100"
                ToolTip="{Binding
                          RelativeSource=
                            {RelativeSource Self},
                             Path=Value}"/>
    </Grid>
</Window>

Figure 5-3 shows the resulting window.

Binding a property of an element to itself

Figure 5-3. Binding a property of an element to itself

Bind to CLR Objects

Problem

You need to bind the properties of UI elements to a CLR object, such as a custom business object.

Solution

To use a CLR object as a binding source, implement a property change notification mechanism such as the System.ComponentModel.INotifyPropertyChanged interface.

How It Works

If you are binding to a CLR object using a System.Windows.Data.BindingMode of either OneWay or TwoWay, you must implement a property change notification mechanism if you want your UI to update dynamically when the source CLR properties change. Such a mechanism is necessary to inform the System.Windows.Data.Binding that it should update the binding target with the new value in the binding source.

The recommended notification mechanism is for the CLR class to implement the INotifyPropertyChanged interface. This interface has just one member, an event called PropertyChanged. When you raise this event, you pass in an instance of the System.

ComponentModel.PropertyChangedEventArgs class. This contains a property called PropertyName, which informs the binding mechanism that the property of the binding source with the specified name has changed its value.

There are alternative notification systems to INotifyPropertyChanged. You can provide change notifications by supporting the PropertyChanged pattern for each property that you want change notifications for. To implement this system, you define a PropertyName Changed event for each property, where PropertyName is the name of the property. You need to raise this event every time the property changes. This was the preferred method to bind to CLR objects in version 1.0 of the .NET Framework and is still supported.

Another option is to back your CLR properties with a corresponding System.Windows.DependencyProperty. These provide built-in support for data binding.

The recommended pattern for implementing INotifyPropertyChanged in a CLR class is as follows. Create a method called OnPropertyChanged that takes the name of the property that has changed as a parameter. In this method, raise the PropertyChanged event, passing in a new instance of the PropertyChangedEventArgs class as the event arguments. Initialize the event arguments with the name of the property passed in to the method. It is common practice to implement this pattern in a base CLR class that all your custom business objects derive from. Then, in the setter part of each property in your class, simply call OnPropertyChanged whenever it is assigned a new value.

The Code

The following example demonstrates a window that data binds to an instance of the Person class in its constructor. It uses three System.Windows.Controls.TextBox controls and a System.Windows.Controls.ComboBox control to display the name, age, and occupation data for a person. These UI elements have two-way data bindings to the corresponding CLR properties of the Person class.

Additionally, there is a System.Windows.Controls.TextBlock control that has a one-way binding to the read-only Description property. The value of this CLR property changes whenever the values of the other properties change. To notify the TextBlock that the description has changed, the Person class implements the INotifyPropertyChanged interface.

In the setters for the properties in the Person class, the OnPropertyChanged method is called twice. It's called first to notify any bound targets that the value of this property has changed. It's called a second time to notify them that the Description property has also changed. The OnProperty-Changed method raises the PropertyChanged event, passing in the property name.

Figure 5-4 shows the resulting window. If the value in any of the TextBox controls or the ComboBox control is changed, then when it loses focus, the description of the person will automatically and dynamically update.

In the code-behind for the window, there is code in the constructor to create and configure an instance of the Person class and assign it to the DataContext of the window.

The XAML for the window is as follows:

<Window
    x:Class="Recipe_05_04.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 5_04" Height="180" Width="260">
    <Grid>
<Grid.ColumnDefinitions>
            <ColumnDefinition Width="74"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="26"/>
            <RowDefinition Height="26"/>
            <RowDefinition Height="26"/>
            <RowDefinition Height="26"/>
            <RowDefinition Height="10"/>
            <RowDefinition Height="26"/>
        </Grid.RowDefinitions>

        <TextBlock
            Margin="4"
            Text="First Name"
            VerticalAlignment="Center"/>
        <TextBox
            Text="{Binding Path=FirstName, Mode=TwoWay}"
            Margin="4" Grid.Column="1"/>

        <TextBlock
            Margin="4"
            Text="Last Name"
            Grid.Row="1"
            VerticalAlignment="Center"/>
        <TextBox
            Margin="4"
            Text="{Binding Path=LastName, Mode=TwoWay}"
            Grid.Column="1" Grid.Row="1"/>

        <TextBlock
            Margin="4"
            Text="Age"
            Grid.Row="2"
            VerticalAlignment="Center"/>
        <TextBox
            Margin="4"
            Text="{Binding Path=Age, Mode=TwoWay}"
            Grid.Column="1"
            Grid.Row="2"/>

        <TextBlock
            Margin="4"
            Text="Occupation"
            Grid.Row="3"
            VerticalAlignment="Center"/>
<ComboBox
           x:Name="cboOccupation"
           IsEditable="False"
           Grid.Column="1"
           Grid.Row="3"
           HorizontalAlignment="Left"
           Text="{Binding Path=Occupation, Mode=TwoWay}"
           Margin="4" Width="140">
            <ComboBoxItem>Student</ComboBoxItem>
            <ComboBoxItem>Skilled</ComboBoxItem>
            <ComboBoxItem>Professional</ComboBoxItem>
       </ComboBox>

       <TextBlock
           Margin="4"
           Text="Description"
           FontWeight="Bold"
           FontStyle="Italic"
           Grid.Row="5"
           VerticalAlignment="Center"/>
       <TextBlock
           Margin="4"
           Text="{Binding Path=Description, UpdateSourceTrigger=PropertyChanged}"
           VerticalAlignment="Center"
           FontStyle="Italic"
           Grid.Column="1"
           Grid.Row="5"/>

    </Grid>
</Window>

The code-behind for the window is as follows:

using System.Windows;

using System.Windows;

namespace Recipe_05_04
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            // Set the DataContext to a Person object
            this.DataContext =
                new Person()
{
                        FirstName = "Elin",
                        LastName = "Binkles",
                        Age = 26,
                        Occupation = "Professional"
                    };
        }
    }
}

The code for the Person class is as follows:

using System.ComponentModel;

namespace Recipe_05_04
{
    public class Person : INotifyPropertyChanged
    {
        private string firstName;
        private string lastName;
        private int age;
        private string occupation;

        // Each property calls the OnPropertyChanged method
        // when its value changed, and each property that
        // affects the Person's Description also calls the
        // OnPropertyChanged method for the Description property.

        public string FirstName
        {
            get
            {
                return firstName;
            }
            set
            {
                if(firstName != value)
                {
                    firstName = value;
                    OnPropertyChanged("FirstName");
                    OnPropertyChanged("Description");
                }
            }
        }
public string LastName
        {
            get
            {
                return lastName;
            }
            set
            {
                if(this.lastName != value)
                {
                    this.lastName = value;
                    OnPropertyChanged("LastName");
                    OnPropertyChanged("Description");
                }
            }
        }

        public int Age
        {
            get
            {
                return age;
            }
            set
            {
                if(this.age != value)
                {
                    this.age = value;
                    OnPropertyChanged("Age");
                    OnPropertyChanged("Description");
                }
            }
        }

        public string Occupation
        {
            get { return occupation; }
            set
            {
                if (this.occupation != value)
                {
                    this.occupation = value;
                    OnPropertyChanged("Occupation");
                    OnPropertyChanged("Description");
                }
            }
        }
// The Description property is read-only
        // and is composed of the values of the
        // other properties.
        public string Description
        {
            get
            {
                return string.Format("{0} {1}, {2} ({3})",
                                     firstName, lastName, age, occupation);
            }
        }

        #region INotifyPropertyChanged Members

        /// Implement INotifyPropertyChanged to notify the binding
        /// targets when the values of properties change.
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(
            string propertyName)
        {
            if(this.PropertyChanged != null)
            {
                // Raise the PropertyChanged event
                this.PropertyChanged(
                    this,
                    new PropertyChangedEventArgs(
                        propertyName));
            }
        }

        #endregion
    }
}

Figure 5-4 shows the resulting window.

Binding to CLR objects

Figure 5-4. Binding to CLR objects

Bind to an Existing Object Instance

Problem

You need to bind a number of UI elements to an object that can be created and populated only at runtime, for example, a custom business object containing live data or a System.Data.DataSet object that's created in response to a database query.

Solution

Create System.Windows.Data.Binding statements in the XAML for your elements to bind the properties of your UI elements to the properties of your data source. Use the Path property of the Binding class to specify the name of the property of the data source to bind to, but do not specify a value for its Source property.

At runtime, assign an existing object instance to the DataContext property of a System. Windows.FrameworkElement. This FrameworkElement must be a UI element that is a parent element of all the child elements that need to bind to it.

Tip

This is also the recommended method of data binding when you need to bind more than one property to a particular source. Because the DataContext of a parent element is inherited by all its child elements, as explained in the "How It Works" section, you don't need to specify the source multiple times. However, when you need to bind only one property to a source, it can be simpler and more convenient to define a data source as a static resource and reference it in the Source property of the binding. This can be easier to debug, because you can see all the information about the binding in one place, instead of having to search for the nearest DataContext to understand what is happening.

How It Works

In this chapter, you have so far seen three different ways of specifying the data source of a Binding: using its ElementName, RelativeSource, and Source properties. Table 5-3 lists these different options.

Table 5-3. Ways of Specifying the Data Source for a Binding

Property

Description

Source

Use this to reference an instance of an object created as a resource.

RelativeSource

Use this to specify a UI element that is relative to the binding target.

ElementName

Use this to specify another UI element on your application.

However, if none of these properties has been set, the binding system will traverse up the tree of elements, looking for the nearest one with a value for its DataContext property. This allows a DataContext to be established for one root element and then inherited automatically by all its child elements.

Setting the DataContext of a FrameworkElement programmatically at runtime automatically updates any inherited bindings on its child elements. This makes it an ideal candidate to use when binding multiple elements to multiple properties of the same source object. It also makes it ideal when you want to bind your controls to a different instance of the same class at runtime.

The Code

The following example demonstrates a window containing three System.Windows.Controls.TextBox objects that display the name and age data for a person. The Person class is defined in the code-behind for the window and represents a simple custom business object.

In the XAML for the window, the Text property of each TextBox is set to a Binding. Each Binding specifies a property of the Person class as its Path but doesn't specify anything for its Source.

In the code-behind for the window, there is code in the constructor to create and configure an instance of the Person class and assign it to the DataContext of the window.

The XAML for the window is as follows:

<Window x:Class="Recipe_05_05.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 5_05" Height="120" Width="300">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>a
            <RowDefinition Height="26"/>
            <RowDefinition Height="26"/>
            <RowDefinition Height="26"/>
        </Grid.RowDefinitions>

        <TextBlock Margin="4" Text="First Name" VerticalAlignment="Center"/>
        <TextBox Margin="4" Text="{Binding Path=FirstName}"
                 Grid.Column="1"/>

        <TextBlock
                  Margin="4" Text="Last Name"
                  Grid.Row="1" VerticalAlignment="Center"/>
        <TextBox Margin="4" Text="{Binding Path=LastName}"
                 Grid.Column="1"
                 Grid.Row="1"/>
<TextBlock Margin="4" Text="Age" Grid.Row="2" VerticalAlignment="Center"/>
        <TextBox Margin="4" Text="{Binding Path=Age}"
                 Grid.Column="1"
                 Grid.Row="2"/>
   </Grid>
</Window>

The code-behind for the window is as follows:

using System.Windows;
using Recipe_05_05;

namespace Recipe_05_05
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            // Set the DataContext to a Person object
            this.DataContext = new Person()
                                   {
                                       FirstName = "Nelly",
                                       LastName = "Blinks",
                                       Age = 26
                                   };
        }
    }
}

Figure 5-5 shows the resulting window.

Binding a number of controls to an existing object instance

Figure 5-5. Binding a number of controls to an existing object instance

Bind to XML Data

Problem

You need to display XML data in a WPF control.

Solution

Create a System.Windows.Data.XmlDataProvider in the System.Windows.ResourceDictionary for your window, and either embed the XML data inline as a data island or set the Source property to reference an embedded XML file and then use this XmlDataProvider as the Source of a binding.

How It Works

The XmlDataProvider class provides a simple way to create a bindable data source from XML data. It can be declared in a Resources section and then referenced via a key. Its data can be declared either inline via the XmlDataProvider's Content property; or, if the XML resides in a separate file, you can use the Source property to reference it via an appropriate Uri.

The XmlDataProvider can be referenced in a binding as a static resource, and the XPath property of the binding can be used to set an XPath query to populate it with the required subset of data. XPath, short for XML Path Language, is a W3C Recommendation published at http://www.w3.org/TR/xpath.

Tip

When embedding XML data directly into the Content property of an XmlDataProvider, it must be given an empty xmlns attribute, or your XPath queries will not work as expected. Instead, they will be qualified by the System.Windows namespace, and your output window will show that a System.Windows. Data.Error exception has occurred.

The Code

The following example demonstrates a window that creates an XmlDataProvider as a static resource and sets its content to embedded XML data containing a list of countries. The XmlDataProvider is given a key and is then referenced in the ItemsSource property of a System.Windows.Controls.ListBox.

The XPath property of the ListBox specifies that the relevant data is the Name attribute of each Country in Countries.

<Window x:Class="Recipe_05_06.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 5_06" Height="240" Width="200">
    <Window.Resources>

        <!-- Use the Source attribute to specify an embedded XML Data File-->
        <!--<XmlDataProvider x:Key="CountriesXML"
                             Source="Countries.xml"
                             XPath="Countries"/>-->

        <!-- Or embed the data directly -->
        <XmlDataProvider x:Key="CountriesXML">
            <x:XData>
<Countries xmlns="" >
                   <Country Name="Great Britan" Continent="Europe" />
                   <Country Name="USA" Continent="NorthAmerica" />
                   <Country Name="Canada" Continent="NorthAmerica" />
                   <Country Name="France" Continent="Europe" />
                   <Country Name="Germany" Continent="Europe" />
                   <Country Name="Italy" Continent="Europe" />
                   <Country Name="Spain" Continent="Europe" />
                   <Country Name="Brazil" Continent="SouthAmerica" />
                   <Country Name="Argentina" Continent="SouthAmerica" />
                   <Country Name="China" Continent="Asia" />
                   <Country Name="India" Continent="Asia" />
                   <Country Name="Japan" Continent="Asia" />
                   <Country Name="South Africa" Continent="Africa" />
                   <Country Name="Tunisia" Continent="Africa" />
                   <Country Name="Egypt" Continent="Africa" />
               </Countries>
           </x:XData>
       </XmlDataProvider>
   </Window.Resources>
   <Grid>
       <ListBox
           ItemsSource="{Binding Source={StaticResource CountriesXML},
           XPath=/Countries/Country/@Name}"
       />
   </Grid>
</Window>

Figure 5-6 shows the resulting window.

Binding to XML data in a control

Figure 5-6. Binding to XML data in a control

Bind to a Method

Problem

You need to bind a property of a UI element to the value returned by a method.

Solution

Use the System.Windows.Data.ObjectDataProvider class to make a method of a class available as a binding source, and bind to its results.

How It Works

The ObjectDataProvider can be created as a resource in your window or control and acts as a wrapper to expose a method as a binding source. Use its ObjectType and MethodName properties to specify the names of the class and the method to bind to. Then simply reference the ObjectDataProvider in a binding statement, and the target property will receive the return value of the method.

If the method expects any parameters, they must be declared in the ObjectDataProvider's MethodParameters collection. To specify the values of the parameters to pass to the method, you can create separate bindings that pull in the values from other UI elements. To do this, create binding statements that reference the ObjectDataProvider as the binding source. Then set the Path attribute to the relevant item in the MethodParameters collection. Set the BindsDirectlyToSource property of the ObjectDataProvider to True. This signals to the ObjectDataProvider that the binding Path statement should be evaluated relative to itself, not to the data item it wraps.

The Code

The following example demonstrates a window containing an ObjectDataProvider that creates a binding source for a method called Convert on the DistanceConverter class. The purpose of this method is to convert miles into kilometers, and vice versa. It takes two parameters: a double specifying the amount and a DistanceType enumeration that represents the unit that the amount is in. These parameters are declared in the ObjectDataProvider's MethodParameters collection.

The window displays a System.Windows.Controls.TextBlock control that binds to the result of the method. There is also a System.Windows.Controls.TextBox control and a System.Windows. Controls.ComboBox control. These bind to the first and second parameters of the method, respectively.

The XAML for the window is as follows:

<Window
    x:Class="Recipe_05_07.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:system="clr-namespace:System;assembly=mscorlib"
    xmlns:Recipe_05_07="clr-namespace:Recipe_05_07"
Title="WPF Recipes 5_07" Width="240" Height="150" >
    <Window.Resources>

        <Recipe_05_07:DoubleToString x:Key="doubleToString" />

        <!-- The ObjectDataProvider exposes the method as a binding source -->
        <ObjectDataProvider
            x:Key="convertDistance"
            ObjectType="{x:Type Recipe_05_07:DistanceConverter }"
            MethodName="Convert" >

            <!-- Declare the parameters the method expects-->
            <ObjectDataProvider.MethodParameters>
                <system:Double>0</system:Double>
                <Recipe_05_07:DistanceType>Miles</Recipe_05_07:DistanceType>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>

    </Window.Resources>

    <Grid Margin="10">

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0.5*"/>
            <ColumnDefinition Width="0.5*"/>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="31" />
            <RowDefinition Height="31" />
            <RowDefinition Height="31" />
        </Grid.RowDefinitions>

        <TextBlock Margin="5" Grid.ColumnSpan="2"
                   VerticalAlignment="Center"
                   Text="Enter a distance to convert:"/>

         <!-- This TextBox binds to the 1st paramter of the method -->
         <TextBox
             Grid.Row="1" Grid.Column="0" Margin="5"
             Text ="{Binding
                     Source={StaticResource convertDistance},
                     Path=MethodParameters[0],
                     BindsDirectlyToSource=true,
                     UpdateSourceTrigger=PropertyChanged,
                     Converter={StaticResource doubleToString}}"/>
<!-- This TextBox binds to the 1st paramter of the method -->
         <ComboBox
             Grid.Row="1" Grid.Column="1" Margin="5" Width="80"
             HorizontalAlignment="Left"
             SelectedValue="{Binding
                             Source={StaticResource convertDistance},
                             Path=MethodParameters[1],
                             BindsDirectlyToSource=true}" >
             <Recipe_05_07:DistanceType>Miles</Recipe_05_07:DistanceType>
             <Recipe_05_07:DistanceType>Kilometres</Recipe_05_07:DistanceType>
         </ComboBox>

         <TextBlock Grid.Row="2" HorizontalAlignment="Right" Margin="5"
                    Text="Result:"/>

         <!-- The TextBlock that binds to the results of the method.-->
         <TextBlock
              Grid.Row="2" Grid.Column="1" Margin="5"
              Text="{Binding
                     Source={StaticResource convertDistance}}"/>
    </Grid>
</Window>

The code for the DistanceConverter class is as follows:

using System;

namespace Recipe_05_07
{
    public enum DistanceType
    {
        Miles,
        Kilometres
    }

    public class DistanceConverter
    {
        /// <summary>
        /// Convert miles to kilometres and vice versa.
        /// </summary>
        /// <param name="amount">The amount to convert.</param>
        /// <param name="distancetype">The units the amount is in.</param>
        /// <returns>A string containing the converted amount.</returns>
        public string Convert(
            double amount,
            DistanceType distancetype)
{
            if(distancetype == DistanceType.Miles)
                return (amount * 1.609344).ToString("0.##") + " km";

            if(distancetype == DistanceType.Kilometres)
                return (amount * 0.621371192).ToString("0.##") + " m";

            throw new ArgumentOutOfRangeException("distanceType");
        }
    }
}

Figure 5-7 shows the resulting window.

Binding to a method and its parameters

Figure 5-7. Binding to a method and its parameters

Bind to a Command

Problem

You need to bind a System.Windows.Controls.Button control directly to a System.Windows. Input.ICommand. This enables you to execute custom logic when the Button is clicked, without having to handle its Click event and call a method. You can also bind the IsEnabled property of the Button to the ICommand object's CanExecute method.

Solution

Create a class that implements ICommand, and expose an instance of it as a property on another class or business object. Bind this property to a Button control's Command property.

How It Works

The Button control derives from the System.Windows.Controls.Primitives.ButtonBase class. This implements the System.Windows.Input.ICommandSource interface and exposes an ICommand property called Command. The ICommand interface encapsulates a unit of functionality. When its Execute method is called, this functionality is executed. The CanExecute method determines whether the ICommand can be executed in its current state. It returns True if the ICommand can be executed and returns False if not.

To execute custom application logic when a Button is clicked, you would typically attach an event handler to its Click event. However, you can also encapsulate this custom logic in a command and bind it directly to the Button control's Command property. This approach has several advantages. First, the IsEnabled property of the Button will automatically be bound to the CanExecute method of the ICommand. This means that when the CanExecuteChanged event is fired, the Button will call the command's CanExecute method and refresh its own IsEnabled property dynamically. Second, the application functionality that should be executed when the Button is clicked does not have to reside in the code-behind for the window. This enables greater separation of presentation and business logic, which is always desirable in object-oriented programming in general, and even more so in WPF development, because it makes it easier for UI designers to work alongside developers without getting in each other's way.

To bind the Command property of a Button to an instance of an ICommand, simply set the Path attribute to the name of the ICommand property, just as you would any other property. You can also optionally specify parameters using the CommandParameter attribute. This in turn can be bound to the properties of other elements and is passed to the Execute and CanExecute methods of the command.

The Code

The following example demonstrates a window containing three System.Windows.Controls.TextBox controls. These are bound to the FirstName, LastName, and Age properties of a custom Person object. The Person class also exposes an instance of the AddPersonCommand and SetOccupationCommand as read-only properties. There are two Button controls on the window that have their Command attribute bound to these command properties. Custom logic in the CanExecute methods of the commands specifies when the Buttons should be enabled or disabled. If the ICommand can be executed and the Button should therefore be enabled, the code in the CanExecute method returns True. If it returns False, the Button will be disabled. The Set Occupation Button control also binds its CommandParameter to the Text property of a System. Windows.Controls.ComboBox control. This demonstrates how to pass parameters to an instance of an ICommand.

The XAML for the window is as follows:

<Window x:Class="Recipe_05_08.Window1"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   Title="WPF Recipes 5_08" Height="224" Width="300">
    <Grid>
           <Grid.ColumnDefinitions>
                  <ColumnDefinition Width="60"/>
                  <ColumnDefinition Width="*"/>
           </Grid.ColumnDefinitions>

           <Grid.RowDefinitions>
                  <RowDefinition Height="26"/>
                  <RowDefinition Height="26"/>
                  <RowDefinition Height="26"/>
                  <RowDefinition Height="40"/>
<RowDefinition Height="34"/>
                  <RowDefinition Height="26"/>
           </Grid.RowDefinitions>

           <TextBlock
               Margin="4"
               Text="First Name"
               VerticalAlignment="Center"/>
           <TextBox
               Text="{Binding Path=FirstName}"
               Margin="4" Grid.Column="1"/>

           <TextBlock
               Margin="4"
               Text="Last Name"
               Grid.Row="1"
               VerticalAlignment="Center"/>
           <TextBox
               Margin="4"
               Text="{Binding Path=LastName}"
               Grid.Column="1" Grid.Row="1"/>

           <TextBlock
               Margin="4"
               Text="Age"
               Grid.Row="2"
               VerticalAlignment="Center"/>
           <TextBox
               Margin="4"
               Text="{Binding Path=Age}"
               Grid.Column="1"
               Grid.Row="2"/>

           <!-- Bind the Button to the Add Command -->
           <Button
               Command="{Binding Path=Add}"
               Content="Add"
               Margin="4"
               Grid.Row="3"
               Grid.Column="2"/>

           <StackPanel
               Orientation="Horizontal"
               Grid.Column="2"
               Grid.Row="4">
<ComboBox
               x:Name="cboOccupation"
               IsEditable="False"
               Margin="4" Width="100">
                <ComboBoxItem>Student</ComboBoxItem>
                <ComboBoxItem>Skilled</ComboBoxItem>
                <ComboBoxItem>Professional</ComboBoxItem>
           </ComboBox>

            <Button
               Command="{Binding Path=SetOccupation}"
               CommandParameter="{Binding ElementName=cboOccupation, Path=Text}"
               Content="Set Occupation"
               Margin="4"
            />

          </StackPanel>

          <TextBlock
              Margin="4"
              Text="Status"
              Grid.Row="5"
              VerticalAlignment="Center"/>
          <TextBlock
              Margin="4"
              Text="{Binding Path=Status, UpdateSourceTrigger=PropertyChanged}"
              VerticalAlignment="Center"
              FontStyle="Italic"
              Grid.Column="1"
              Grid.Row="5"/>

    </Grid>
</Window>

The code-behind for the window sets its DataContext property to a new Person object. The code for this is as follows:

using System.Windows;
using Recipe_05_08;

namespace Recipe_05_08
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
// Set the DataContext to a Person object
            this.DataContext =
                new Person()
                    {
                        FirstName = "Ellin",
                        LastName = "Blinks",
                    };
        }
    }
}

The code for the Person class, which also contains the command classes, is as follows:

using System;
using System.ComponentModel;
using System.Windows.Input;

namespace Recipe_05_08
{
    public class Person : INotifyPropertyChanged
    {
        private string firstName;
        private int age;
        private string lastName;
        private string status;
        private string occupation;

        private AddPersonCommand addPersonCommand;
        private SetOccupationCommand setOccupationCommand;

        public string FirstName
        {
            get
            {
                return firstName;
            }
            set
            {
                if(firstName != value)
                {
                    firstName = value;
                    OnPropertyChanged("FirstName");
                }
            }
        }
public string LastName
        {
            get
            {
                return lastName;
            }
            set
            {
                 if(this.lastName != value)
                 {
                     this.lastName = value;
                     OnPropertyChanged("LastName");
                 }
             }
         }

         public int Age
         {
             get
             {
                 return age;
             }
             set
             {
                 if(this.age != value)
                 {
                     this.age = value;
                     OnPropertyChanged("Age");
                 }
             }
         }

         public string Status
         {
             get
             {
                 return status;
             }
             set
             {
                 if(this.status != value)
                 {
                     this.status = value;
                     OnPropertyChanged("Status");
                 }
             }
         }
public string Occupation
         {
             get
             {
                return occupation;
             }
             set
             {
                if(this.occupation != value)
                {
                    this.occupation = value;
                    OnPropertyChanged("Occupation");
                }
            }
        }

        /// Gets an AddPersonCommand for data binding
        public AddPersonCommand Add
        {
            get
            {
                if(addPersonCommand == null)
                    addPersonCommand = new AddPersonCommand(this);

                return addPersonCommand;
            }
        }

        /// Gets a SetOccupationCommand for data binding
        public SetOccupationCommand SetOccupation
        {
            get
            {
                if(setOccupationCommand == null)
                    setOccupationCommand = new SetOccupationCommand(this);

                return setOccupationCommand;
            }
        }

        #region INotifyPropertyChanged Members

        /// Implement INotifyPropertyChanged to notify the binding
        /// targets when the values of properties change.
        public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
        {
            if(this.PropertyChanged != null)
            {
                this.PropertyChanged(
                    this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
   }

   public class AddPersonCommand : ICommand
   {
       private Person person;

       public AddPersonCommand(Person person)
       {
           this.person = person;

           this.person.PropertyChanged +=
               new PropertyChangedEventHandler(person_PropertyChanged);
       }

       // Handle the PropertyChanged event of the person to raise the
       // CanExecuteChanged event
       private void person_PropertyChanged(
           object sender, PropertyChangedEventArgs e)
       {
           if(CanExecuteChanged != null)
           {
               CanExecuteChanged(this, EventArgs.Empty);
           }
       }

       #region ICommand Members

       /// The command can execute if there are valid values
       /// for the person's FirstName, LastName, and Age properties
       /// and if it hasn't already been executed and had its
       /// Status property set.
       public bool CanExecute(object parameter)
       {
           if(!string.IsNullOrEmpty(person.FirstName))
               if(!string.IsNullOrEmpty(person.LastName))
                   if(person.Age > 0)
                       if(string.IsNullOrEmpty(person.Status))
                           return true;
return false;
       }

       public event EventHandler CanExecuteChanged;

       /// When the command is executed, update the
       /// status property of the person.
       public void Execute(object parameter)
       {
           person.Status =
               string.Format("Added {0} {1}",
                             person.FirstName, person.LastName);
       }

       #endregion
}

public class SetOccupationCommand : ICommand
{
    private Person person;

    public SetOccupationCommand(Person person)
    {
        this.person = person;

        this.person.PropertyChanged +=
            new PropertyChangedEventHandler(person_PropertyChanged);
    }

    // Handle the PropertyChanged event of the person to raise the
    // CanExecuteChanged event
    private void person_PropertyChanged(
        object sender, PropertyChangedEventArgs e)
    {
        if(CanExecuteChanged != null)
        {
            CanExecuteChanged(this, EventArgs.Empty);
        }
     }

     #region ICommand Members

     /// The command can execute if the person has been added,
     /// which means its Status will be set, and if the occupation
/// parameter is not null
     public bool CanExecute(object parameter)
     {
         if(!string.IsNullOrEmpty(parameter as string))
             if(!string.IsNullOrEmpty(person.Status))
                 return true;

         return false;
     }

     public event EventHandler CanExecuteChanged;

     /// When the command is executed, set the Occupation
     /// property of the person, and update the Status.
     public void Execute(object parameter)
     {
         // Get the occupation string from the command parameter
         person.Occupation = parameter.ToString();

         person.Status =
             string.Format("Added {0} {1}, {2}",
                           person.FirstName, person.LastName, person.Occupation);
     }
     #endregion
  }
}

Figure 5-8 shows the resulting window.

Binding to a command

Figure 5-8. Binding to a command

Bind to the Values of an Enumeration

Problem

You need to bind a System.Windows.Controls.ItemsControl to all the possible values of an enumeration.

Solution

Use the System.Windows.Data.ObjectDataProvider class to make the values of a System.Enum available as a binding source. Bind the ObjectDataProvider to the ItemsSource property of an ItemsControl.

How It Works

You can create the ObjectDataProvider as a resource in your window or control and can expose the values of an Enum as a binding source. In your XAML, declare an ObjectDataProvider, and set the MethodName and ObjectType attributes to the GetValues method of the System.Enum class. Add the type of the Enum you want to convert to the MethodParameters collection of the ObjectDataProvider. Then simply bind the ItemsSource property of an ItemsControl, such as a System.Windows.Controls.ComboBox or System.Windows.Controls.ListBox control, to this ObjectDataProvider.

The Code

The following example demonstrates a window containing an ObjectDataProvider that creates a binding source for an enumeration called DaysOfTheWeek. Unsurprisingly, this enumerates the days of the week, from Monday to Sunday. The window contains a ComboBox that binds to the ObjectDataProvider and displays the values of this Enum.

The XAML for the window is as follows:

<Window
    x:Class="Recipe_05_09.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:System="clr-namespace:System;assembly=mscorlib"
    xmlns:Recipe_05_09="clr-namespace:Recipe_05_09"
    Title="WPF Recipes 5_09" Height="100" Width="180">

    <Window.Resources>

        <!-- The ObjectDataProvider exposes the enum as a binding source -->
        <ObjectDataProvider
            x:Key="daysData"
            MethodName="GetValues"
            ObjectType="{x:Type System:Enum}" >
<!-- Pass the DaysOfTheWeek type to the -->
        <!-- GetValues property of System.Enum. -->
        <ObjectDataProvider.MethodParameters>
            <x:Type TypeName="Recipe_05_09:DaysOfTheWeek"/>
        </ObjectDataProvider.MethodParameters>
     </ObjectDataProvider>

   </Window.Resources>

   <StackPanel>
       <TextBlock
           Margin="5"
           Text="Select the day of the week:"/>

       <!-- Binds to the ObjectDataProvider -->
       <ComboBox
           Margin="5"
           ItemsSource="{Binding
                         Source={StaticResource daysData}}" />
   </StackPanel>
</Window>

The DaysOfTheWeek enumeration is declared in the code-behind for the window, which is as follows:

using System.Windows;

namespace Recipe_05_09
{
    /// <summary>
    /// The Days of the Week enumeration
    /// </summary>
    public enum DaysOfTheWeek
    {
        Monday,
        Tuesday,
        Wednesday,
        Thursday,
        Friday,
        Saturday,
        Sunday
    }
public partial class Window1 : Window
   {

       public Window1()
       {
           InitializeComponent();
       }
   }
}

Figure 5-9 shows the resulting window.

Binding to the values of an enumeration

Figure 5-9. Binding to the values of an enumeration

Specify a Default Value for a Binding

Problem

You need to specify a default value for a data binding as a fallback in case the source property of the binding cannot always be resolved.

Solution

Use the FallbackValue property of System.Windows.Data.BindingBase.

How It Works

The FallbackValue property specifies the value to use when the binding is unable to return a value. A binding may be unable to return a value for any of the following reasons:

  • The path to the binding cannot be resolved successfully.

  • The value converter, if there is one, cannot convert the resulting value.

  • The resulting value is not valid for the binding target property.

In any of these cases, the target property is set to the value of the FallbackValue, if one is available.

Tip

Specifying a default value for a binding can be very useful when working with design tools such as Microsoft Expression Blend. If the data source for a binding is assigned only at runtime, then the target property will not display anything in design mode. This means designers don't see a realistic view of the UI they are designing.

The Code

The following example demonstrates a window containing three System.Windows.Controls.TextBox objects that display the name and age of a person. The window is never actually assigned a data source, so when the application is run, the TextBox objects are empty by default. However, in the binding statement for each TextBox, the FallbackValue is specified. This ensures that when the application is run, the TextBox objects display default values.

The XAML for the window is as follows:

<Window x:Class="Recipe_05_10.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     Title="WPF Recipes 5_10" Height="120" Width="300">
    <Grid>
       <Grid.ColumnDefinitions>
           <ColumnDefinition Width="60"/>
           <ColumnDefinition Width="*"/>
       </Grid.ColumnDefinitions>

       <Grid.RowDefinitions>
           <RowDefinition Height="28"/>
           <RowDefinition Height="28"/>
           <RowDefinition Height="28"/>
       </Grid.RowDefinitions>

       <TextBlock Margin="4" Text="First Name" VerticalAlignment="Center"/>
       <TextBox Margin="4"
                      Text="{Binding
                                  Path=FirstName,
                                  FallbackValue=First name goes here}"
                      FontStyle="Italic" Grid.Column="1"/>

       <TextBlock Margin="4" Text="Last Name"
                       Grid.Row="1" VerticalAlignment="Center"/>
<TextBox Margin="4"
                       Text="{Binding
                                  Path=LastName,
                                  FallbackValue=Second name goes here}"
                       FontStyle="Italic"
                       Grid.Column="1" Grid.Row="1"/>

       <TextBlock Margin="4" Text="Age"
                         Grid.Row="2" VerticalAlignment="Center"/>
       <TextBox Margin="4"
                      Text="{Binding
                                  Path=Age,
                                  FallbackValue=Age goes here}"
                      FontStyle="Italic"
                      Grid.Column="1" Grid.Row="2"/>
    </Grid>
</Window>

Figure 5-10 shows the resulting window.

Specifying default values for a binding

Figure 5-10. Specifying default values for a binding

Use Data Templates to Display Bound Data

Problem

You need to specify a set of UI elements to use to visualize your bound data objects.

Solution

Create a System.Windows.DataTemplate to define the presentation of your data objects. This specifies the visual structure of UI elements to use to display your data.

How It Works

When you bind to a data object, the binding target displays a string representation of the object by default. Internally, this is because without any specific instructions the binding mechanism calls the ToString method of the binding source when binding to it. Creating a DataTemplate enables you to specify a different visual structure of UI elements when displaying your data object. When the binding mechanism is asked to display a data object, it will use the UI elements specified in the DataTemplate to render it.

The Code

The following example demonstrates a window that contains a System.Windows.Controls.ListBox control. The ItemsSource property of the ListBox is bound to a collection of Person objects. The Person class is defined in the Data.cs file and exposes FirstName, LastName, Age and Photo properties. It also overrides the ToString method to return the full name of the person it represents. Without a DataTemplate, the ListBox control would just display this list of names. Figure 5-11 shows what this would look like.

Binding to a list of data objects, without specifying a DataTemplate

Figure 5-11. Binding to a list of data objects, without specifying a DataTemplate

However, the ItemTemplate property of the ListBox is set to a static resource called personTemplate. This is a DataTemplate resource defined in the window's System.Windows.ResourceDictionary. The DataTemplate creates a System.Windows.Controls.Grid control inside a System.Windows.Controls.Border control. Inside the Grid, it defines a series of System. Windows.Controls.TextBlock controls and a System.Windows.Controls.Image control. These controls have standard binding statements that bind their properties to properties on the Person class. When the window opens and the ListBox binds to the collection of Person objects, the binding mechanism uses the set of UI elements in the DataTemplate to display each item. Figure 5-12 shows the same ListBox as in Figure 5-11 but with its ItemTemplate property set to the DataTemplate.

The XAML for the window is as follows:

<Window
    x:Class="Recipe_05_11.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Recipe_05_11="clr-namespace:Recipe_05_11"
    Title="WPF Recipes 5_11" Height="298" Width="260">

    <Window.Resources>
<!-- Creates the local data source for binding -->
    <Recipe_05_11:People x:Key="people"/>

    <!-- Styles used by the UI elements in the DataTemplate -->
    <Style
        x:Key="lblStyle"
        TargetType="{x:Type TextBlock}">
        <Setter Property="FontFamily" Value="Tahoma"/>
        <Setter Property="FontSize" Value="11pt"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="Margin" Value="2"/>
        <Setter Property="Foreground" Value="Red"/>
    </Style>

    <Style
        x:Key="dataStyle"
        TargetType="{x:Type TextBlock}"
        BasedOn="{StaticResource lblStyle}">
        <Setter Property="Margin" Value="10,2,2,2"/>
        <Setter Property="Foreground" Value="Blue"/>
        <Setter Property="FontStyle" Value="Italic"/>
    </Style>

    <!-- DataTemplate to use for displaying each Person item -->
    <DataTemplate x:Key="personTemplate">
        <Border
            BorderThickness="1"
            BorderBrush="Gray"
            Padding="4"
            Margin="4"
            Height="Auto"
            Width="Auto">
            <Grid>
                <Grid.ColumnDefinitions>

                    <ColumnDefinition Width="80"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>

                <StackPanel>
                    <TextBlock
                        Style="{StaticResource lblStyle}"
                        Text="First Name" />
                <TextBlock
                    Style="{StaticResource dataStyle}"
                    Text="{Binding Path=FirstName}"/>
<TextBlock
                    Style="{StaticResource lblStyle}"
                    Text="Last Name" />
                <TextBlock
                    Style="{StaticResource dataStyle}"
                    Text="{Binding Path=LastName}" />

                <TextBlock
                    Style="{StaticResource lblStyle}"
                    Text="Age" />
                <TextBlock
                    Style="{StaticResource dataStyle}"
                    Text="{Binding Path=Age}" />
            </StackPanel>

            <Image
                Margin="4"
                Grid.Column="1"
                Width="96"
                Height="140"
                Source="{Binding Path=Photo}"/>
         </Grid>
     </Border>
   </DataTemplate>


</Window.Resources>

<Grid>
    <!-- The ListBox binds to the people collection, and sets the -->
    <!-- DataTemplate to use for displaying each item -->
    <ListBox
        Margin="10"
        ItemsSource="{Binding Source={StaticResource people}}"
        ItemTemplate="{StaticResource personTemplate}"/>

   <!-- Without specifying a DataTemplate, the ListBox just -->
   <!-- displays a list of names. -->
   <!--<ListBox
       Margin="10"
       ItemsSource="{Binding Source={StaticResource people}}"/>-->
 </Grid>
</Window>

Figure 5-12 shows the resulting window.

Binding to a list of data objects and specifying a DataTemplate

Figure 5-12. Binding to a list of data objects and specifying a DataTemplate

Use Value Converters to Convert Bound Data

Problem

You need to convert the source value of a binding in order to assign it to the target value. For example, you need to bind one type of property to a completely different type of property, such as binding an integer value to a System.Windows.Controls.Control.Foreground property. Alternatively, you may need to bind two values of the same type but derive the value of the target property from a calculation based on the value of the source. For example, your data has a property of type double that you want to bind to a System.Windows.FrameworkElement.Width property.

Solution

Create a class that implements the System.Windows.Data.IValueConverter interface. Add custom logic to the Convert method to apply a conversion to the data that will be assigned to the binding. Declare this converter class as a static resource, and reference it as the Converter property of a System.Windows.Data.Binding in your XAML.

Tip

WPF includes a few value converters out of the box for common data binding scenarios. For example, the System.Windows.Controls.BooleanToVisibilityConverter class, which converts a Boolean value to a System.Windows.Visibility value. This is extremely useful for specifying whether a particular UI element and its children should be displayed at runtime, based on the value of a Boolean property.

How It Works

The IValueConverter interface has two methods, Convert and ConvertBack. When you specify the Converter property of a binding, the source value is not bound directly to the target value. Instead, it is passed in as the value parameter to the Convert method of the converter. The Convert method that receives the value is then free to apply conversion logic based on its value. It returns an instance of the type expected by the binding target.

The ConvertBack method is called in a two-way binding, when the binding target propagates a value to the binding source. In one-way bindings, it can simply throw a System.NotImplementedException instance, because it should never be called.

If you need to convert the values from multiple properties into a single value to assign to a binding target, use a System.Windows.Data.IMultiValueConverter. This associates a converter with a System.Windows.Data.MultiBinding class, which attaches a collection of System.Windows. Data.Binding objects to a single binding target property. This is useful when the value of a bound property should be updated whenever the values of multiple source properties change.

Tip

When naming a converter, it is good practice to use the convention <Source Type or Name>To<Target Type>Converter, for example, DoubleToWidthProperty or ProbabilityToOpacityConverter. It is also good practice to decorate the converter class with the System.Windows.Data.ValueConversionAttribute to indicate to development tools the data types involved in the conversion.

The Code

The following example demonstrates a window containing a System.Windows.Controls.ItemsControl that displays a collection of DataItem objects. This object exposes a Percent property, which contains a double value from −100 to +100. The window contains a System.Windows.DataTemplate in its Resources collection. This specifies that each DataItem should be displayed as a System.Windows.Shapes.Rectangle, which has the effect of presenting the data items in the form of a simple bar graph.

The window declares two converter classes. These are referenced in the binding statements in the XAML for the rectangle. The Height property of each rectangle is bound to the Percent property of its DataItem and is sent through an instance of the PercentToHeightConverter. In this case, both the source property, Percent, and the target property, Height, are double values. However, a conversion has to take place to translate the value of Percent, into a valid Height value. The Fill property of the rectangle is also bound to the Percent property of DataItem. However, this binding requires a value converter, because a double value cannot be converted directly to a System.Windows.Media.Brush value. The PercentToFillConverter intercepts the binding and returns one color of Brush if the Percent value is positive and another value if it is negative.

The XAML for the window is as follows:

<Window
    x:Class="Recipe_05_12.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Using_Value_Converters"
    xmlns:Recipe_05_12="clr-namespace:Recipe_05_12"
    x:Name="thisWindow"
    Title="WPF Recipes 5_12" Height="240" Width="280">

    <Window.Resources>

        <local:DataItems x:Key="dataItems"/>

        <!-- Declare two converter classes -->
        <Recipe_05_12:PercentToHeightConverter x:Key="percentToHeightConverter" />
        <Recipe_05_12:PercentToFillConverter x:Key="percentToFillConverter" />

        <!-- Bind the rectangle's height and color to the data's -->
        <!-- Percent property, but apply a conversion -->
        <!-- to it using the two converter classes. -->
        <DataTemplate x:Key="dataItemtemplate">
            <Rectangle
                Margin="4"
                Width="30"
                VerticalAlignment="Bottom"

                Height="{Binding
                         Path=Percent,
                         Converter={StaticResource
                                             percentToHeightConverter}}"
                Fill="{Binding
                         Path=Percent,
                         Converter={StaticResource
                                             percentToFillConverter}}"/>
        </DataTemplate>

    </Window.Resources>

    <Grid Margin="20">

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1"/>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="1"/>
        </Grid.RowDefinitions>
<ItemsControl
            Grid.Column="1"
            Margin="4,0,0,4"
            ItemsSource="{Binding Source={StaticResource dataItems}}"
            ItemTemplate="{StaticResource dataItemtemplate}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>

        <Line Grid.RowSpan="2"
              Stroke="Black"
              StrokeThickness="2"
              X1="0" Y1="0"
              X2="0"
              Y2="{Binding ElementName=thisWindow, Path=ActualHeight}"/>

        <Line Grid.Row="1"
              Grid.ColumnSpan="2"
              Stroke="Black"
              StrokeThickness="2"
              X1="0" Y1="0"
              X2="{Binding ElementName=thisWindow, Path=ActualWidth}"
              Y2="0"/>
     </Grid>
</Window>

The code for the PercentToHeightConverter class is as follows:

using System;
using System.Windows.Data;
using System.Globalization;

namespace Recipe_05_12
{
    [ValueConversion(typeof (double), typeof (double))]
    public class PercentToHeightConverter : IValueConverter
    {
        // Converts a Percent value to a new height value.
        // The data binding engine calls this method when
        // it propagates a value from the binding source to the binding target.
        public Object Convert(
            Object value,
            Type targetType,
            Object parameter,
            CultureInfo culture)
{
         double percent =
             System.Convert.ToDouble(value);

         // if the value is negative, invert it
         if(percent < 0)
             percent *= −1;

         return percent * 2;
     }

     // Converts a value. The data binding engine calls this
     // method when it propagates a value from the binding
     // target to the binding source.
     // As the binding is one-way, this is not implemented.
     public object ConvertBack(
         object value,
         Type targetType,
         object parameter,
         CultureInfo culture)
     {
         throw new NotImplementedException();
     }
   }
}

The code for the PercentToFillConverter is as follows:

using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using System.Globalization;
using System.Collections.Generic;

namespace Recipe_05_12
{
   [ValueConversion(typeof (double), typeof (Brush))]
   public class PercentToFillConverter : IValueConverter
   {
       // Declares a Brush to use for negative data items
       private static readonly Brush negativeColor =
           new LinearGradientBrush(
               new GradientStopCollection(
                   new List<GradientStop>(
                       new GradientStop[]
{
                               new GradientStop(
                                   Color.FromArgb(255, 165, 0, 0), 0),
                               new GradientStop(
                                   Color.FromArgb(255, 132, 0, 0), 0)
                           }
                        )),
                new Point(0.5,0),
                new Point(0.5,1));

     // Declares a Brush to use for positive data items
     private static readonly Brush positiveColor =
         new LinearGradientBrush(
             new GradientStopCollection(
                 new List<GradientStop>(
                     new GradientStop[]
                         {
                             new GradientStop(
                                 Color.FromArgb(255, 0, 165, 39), 1),
                             new GradientStop(
                                 Color.FromArgb(255, 0, 132, 37), 0)
                         }
                     )),
             new Point(0.5, 0),
             new Point(0.5, 1));

     // Converts a Percent value to a Fill value.
     // Returns a Brush based on whether Percent is positive or negative.
     // The data binding engine calls this method when
     // it propagates a value from the binding source to the binding target.
     public Object Convert(
         Object value,
         Type targetType,
         Object parameter,
         CultureInfo culture)
     {

         double percent = System.Convert.ToDouble(value);

         if(percent > 0)
         {
             return positiveColor;
         }
         else
         {
             return negativeColor;
         }
     }
// Converts a value. The data binding engine calls this
     // method when it propagates a value from the binding
     // target to the binding source.
     // As the binding is one-way, this is not implemented.
     public object ConvertBack(
         object value,
         Type targetType,
         object parameter,
         CultureInfo culture)
     {
         throw new NotImplementedException();
     }
   }
}

Figure 5-13 shows the resulting window.

Converting bound data

Figure 5-13. Converting bound data

Use Data Triggers to Change the Appearance of Bound Data

Problem

You need to change the appearance of a bound data object when its property values meet a certain condition. For example, you are binding to a list of products, and you want those items that are out of stock to be given a different visual appearance than the others.

Solution

Create a System.Windows.DataTemplate to define the visual structure of your data object, and add a System.Windows.DataTrigger to it. The DataTrigger sets appearance property values on the UI elements in the DataTemplate when the property value of the data object matches a specified condition.

How It Works

Like the System.Windows.Style and System.Windows.Controls.ControlTemplate classes, the DataTemplate class has a collection of triggers. A trigger applies property values or performs actions when the bound data meets a specified condition.

Create a DataTrigger in the Triggers collection of your DataTemplate. A DataTrigger has three components to configure. First, it has a Binding property that specifies the property of the data object it should be bound to. Set this using a standard binding statement, and assign the name of the property in the binding's Path attribute. It is this property that the trigger will be evaluating to determine whether it should be applied. Second, specify the DataTrigger's Value attribute. This stores the value that the bound property should contain in order for the trigger to be applied. For example, suppose you want to apply a different visual appearance to out-of-stock items in a product catalog. You could create a DataTrigger, set its Binding property to the IsOutOfStock property of the data object, and set its Value property to True.

The third component in a DataTrigger is its Setters property. This contains a collection of System.Windows.Setter objects, which describe the appearance property values to apply when the bound property has the specified value. Each Setter object specifies the UI element in the template to target, the property of that target it should set, and the value to set it to. For example, if you wanted to highlight the names of out-of-stock products in red, you would create a Setter with a TargetName of txtName, a Property of Foreground, and a Value of Red.

A DataTrigger can contain multiple Setter objects that change the visual appearance of the DataTemplate in different ways when the trigger's condition is met. For example, if a product is out of stock, you might want to not only highlight its name in red but also hide the Add to Shopping Basket button.

The Code

The following example demonstrates a window containing a System.Windows.Controls.ItemsControl that displays a collection of DataItem objects. The window contains a System. Windows.DataTemplate in its Resources collection, which specifies that each DataItem should be displayed as a System.Windows.Shapes.Rectangle. This has the effect of presenting the data items in the form of a simple bar graph.

The DataTemplate contains a DataTrigger. This binds to a Boolean property on the DataItem class called IsPositive. When the value of this property is True, the Setter in the DataTrigger changes the Fill color of the rectangle to red.

The XAML for the window is as follows:

<Window x:Class="Recipe_05_13.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Recipe_05_13="clr-namespace:Recipe_05_13"
    x:Name="thisWindow"
    Title="WPF Recipes 5_13" Height="240" Width="280">

    <Window.Resources>

        <Recipe_05_13:DataItems x:Key="dataItems"/>
<!-- Declare two converter classes -->
    <Recipe_05_13:AmountToHeightConverter x:Key="amountToHeightConverter" />

    <!-- Creates a DataTemplate that displays a colored bar -->
    <!-- for each DataItem. Its height is calculated by a converter.-->
    <DataTemplate x:Key="dataItemtemplate">
        <Rectangle
            x:Name="rectangle"
            Margin="4"
            Width="30"
            VerticalAlignment="Bottom"
            Fill="Green"
            Height="{Binding
                     Path=Amount,
                     Converter={StaticResource
                                         amountToHeightConverter}}"/>
    <!-- A DataTigger that binds to the IsPositive property -->
    <!-- of a DataItem, and changes the color of the bar to -->
    <!-- red if IsPositive is False. -->
    <DataTemplate.Triggers>
        <DataTrigger
                Binding="{Binding
                    Path=IsPositive}"
                Value="False">
             <Setter
                 TargetName="rectangle"
                 Property="Fill"
                 Value="Red"/>
        </DataTrigger>
    </DataTemplate.Triggers>
  </DataTemplate>

 </Window.Resources>

 <Grid Margin="20">

     <Grid.ColumnDefinitions>
         <ColumnDefinition Width="1"/>
         <ColumnDefinition />
     </Grid.ColumnDefinitions>

     <Grid.RowDefinitions>
         <RowDefinition/>
         <RowDefinition Height="1"/>
     </Grid.RowDefinitions>
<ItemsControl
         Grid.Column="1"
         Margin="4,0,0,4"
         ItemsSource="{Binding Source={StaticResource dataItems}}"
         ItemTemplate="{StaticResource dataItemtemplate}">
         <ItemsControl.ItemsPanel>
             <ItemsPanelTemplate>
                 <StackPanel Orientation="Horizontal"/>
             </ItemsPanelTemplate>
         </ItemsControl.ItemsPanel>
     </ItemsControl>

     <Line Grid.RowSpan="2"
           Stroke="Black"
           StrokeThickness="2"
           X1="0" Y1="0"
           X2="0"
           Y2="{Binding ElementName=thisWindow, Path=ActualHeight}"/>

     <Line Grid.Row="1"
           Grid.ColumnSpan="2"
           Stroke="Black"
           StrokeThickness="2"
           X1="0" Y1="0"
           X2="{Binding ElementName=thisWindow, Path=ActualWidth}"
           Y2="0"/>
   </Grid>
</Window>

Figure 5-14 shows the resulting window.

Using triggers to change the appearance of a DataTemplate

Figure 5-14. Using triggers to change the appearance of a DataTemplate

Select a DataTemplate Based on Properties of the Data Object

Problem

You need to select a different DataTemplate to display a data object, based on properties of the data object or custom application logic.

Solution

Create two or more System.Windows.DataTemplate instances that define the UI elements to display a data object. Create a class that inherits from System.Windows.Controls.DataTemplateSelector, and override the SelectTemplate method. Supply custom application logic to determine which DataTemplate to return for a given data object. Assign this DataTemplateSelector to the target element in your binding.

How It Works

The DataTemplateSelector class provides a way to choose a DataTemplate, based on the data object and custom application logic. This allows you to define multiple templates and dynamically choose which one to apply to any given data object.

Create a class that derives from DataTemplateSelector, and override the SelectTemplate method. This method takes a parameter called item, which is the instance of your data object for which the binding requires a template. If you are binding to a list of items, it will call this method once for each item in the list. You can define custom application logic to determine which DataTemplate to return. Use the FindResource method of System.Windows.FrameworkElememt to locate the required template resource.

Assign your DataTemplateSelector to an appropriate property of the binding target element, instead of assigning a single DataTemplate to it. For example, instead of setting the ItemTemplate property of a System.Windows.Controls.ListBox control, set its ItemTemplateSelector property.

Several other controls in the standard WPF control suite also expose a DataTemplateSelector property. For example, both the System.Windows.Controls.ContentControl and System. Windows.Controls.ContentPresenter classes expose a DataTemplateSelector via their ContentTemplateSelector properties. Assigning a DataTemplateSelector to either of these properties, or the controls that derive from them, allows you to dynamically choose different UI elements to display their content. The HeaderTemplateSelector property of both System. Windows.Controls.HeaderedItemsControl and System.Windows.Controls.HeaderedContentControl allows you to dynamically select a template to use to display a header. Furthermore, when using a System.Windows.Controls.GridView control, you can select templates for cells and column headers. The System.Windows.Controls.TabControl control also exposes DataTemplateSelector properties for selecting the template to use to display the content of its tabs and its selected tab.

Tip

To make your DataTemplateSelector behave nicely in designers, such as the WPF designer for Visual Studio or Microsoft Expression Blend, return null in the SelectTemplate method at design time. This is because the DataTemplate resources referenced by the FindResource method of FrameworkElement might not be available at design time. This results in an exception being shown when you try to open your window or control in a designer. By checking for design mode and returning null, your controls will display themselves in the designer, albeit without applying your custom DataTemplate instances.

The Code

The following example demonstrates a window containing a System.Windows.Controls.ListBox control that displays a collection of TaskItem objects. The ItemTemplateSelector property of the ListBox is set to a DataTemplateSelector class called TaskItemDataTemplateSelector. In the SelectTemplate method of this class, there is custom logic to check the value of the Priority property of each TaskItem. If this value is 1, it returns a DataTemplate called highPriorityTaskTemplate. If not, it returns a DataTemplate called defaultTaskTemplate.

Both templates are defined in the window's Resources collection. Note that the DataTemplateSelector is also defined as a static resource in this collection.

The XAML for the window is as follows:

<Window
    x:Class="Recipe_05_14.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Recipe_05_14="clr-namespace:Recipe_05_14"
    Title="WPF Recipes 5_14" Height="360" Width="330">
    <Window.Resources>

        <!-- Create the TaskList data -->
        <Recipe_05_14:TaskList x:Key="taskList"/>

        <!-- Create the DataTemplateSelector -->
        <Recipe_05_14:TaskItemDataTemplateSelector
                                x:Key="taskItemDataTemplateSelector"/>

        <!-- Default DataTemplate for tasks -->
        <DataTemplate
            x:Key="defaultTaskTemplate">
            <Border Name="border"
                    BorderBrush="LightBlue"
                    BorderThickness="1"
                    Padding="5"
                    Margin="5">
<Grid>
           <Grid.RowDefinitions>
               <RowDefinition/>
               <RowDefinition/>
           </Grid.RowDefinitions>
           <Grid.ColumnDefinitions>
               <ColumnDefinition Width="80" />
               <ColumnDefinition />
           </Grid.ColumnDefinitions>
           <TextBlock Grid.Row="0" Grid.Column="0"
                      Text="Name:"/>
           <TextBlock Grid.Row="0" Grid.Column="1"
                      Text="{Binding Path=Name}" />
           <TextBlock Grid.Row="1" Grid.Column="0"
                      Text="Description:"/>
           <TextBlock Grid.Row="1" Grid.Column="1"
                      Text="{Binding Path=Description}"/>
        </Grid>
    </Border>
</DataTemplate>

<!-- DataTemplate for high priority tasks -->
<DataTemplate
    x:Key="highPriorityTaskTemplate">

    <Border
        Name="border"
        BorderBrush="Red"
        BorderThickness="2"
        Margin="5">
        <DockPanel
            Margin="4"
            HorizontalAlignment="Center">
            <TextBlock
                FontSize="18"
                Text="{Binding Path=Description}" />
            <Image
                Margin="20,4,4,4"
                Height="55" Width="39"
                Source="Exclamation.png"/>
        </DockPanel>
    </Border>
</DataTemplate>
</Window.Resources>

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="24"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <TextBlock
        Margin="4"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        FontSize="14"
        Text="Task List:"/>

    <!-- Bind the ListBox to the data -->
    <!-- and assign the DataTemplateSelector -->
    <ListBox
        Margin="10"
        Grid.Row="1"
        HorizontalContentAlignment="Stretch"
        ItemsSource="{Binding
                      Source={StaticResource taskList}}"
        ItemTemplateSelector="{StaticResource
                                taskItemDataTemplateSelector}"/>
  </Grid>
</Window>

The code for the DataTemplateSelector is as follows:

using System.Windows;
using System.Windows.Controls;
using Recipe_05_14;

namespace Recipe_05_14
{
    public class TaskItemDataTemplateSelector
        : DataTemplateSelector
    {
        // Override the SelectTemplate method to
        // return the desired DataTemplate.
        //
        public override DataTemplate SelectTemplate(
            object item,
            DependencyObject container)
{
         if (item != null &&
           item is TaskItem)
         {
            TaskItem taskitem = item as TaskItem;

            Window window = Application.Current.MainWindow;

            // To run in design mode test for design mode, and
            // return null, as it will not find the DataTemplate resources
            // in the following code.
            if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(
                window))
                return null;

            // Check the Priority of the TaskItem to
            // determine the DataTemplate to display it.
            //
            if (taskitem.Priority == 1)
            {
                // Use the window's FindResource method to
                // locate the DataTemplate
                return
                    window.FindResource(
                        "highPriorityTaskTemplate") as DataTemplate;
            }
            else
            {
                return
                    window.FindResource(
                        "defaultTaskTemplate") as DataTemplate;
            }
         }

         return null;
       }
   }
}

Figure 5-15 shows the resulting window.

Selecting a DataTemplate dynamically

Figure 5-15. Selecting a DataTemplate dynamically

Specify Validation Rules for a Binding

Problem

You need to validate user input in a bound UI element, reject invalid data, and give feedback to the user as to why the input is invalid.

Solution

Create a class that derives from System.Windows.Controls.ValidationRule, and specify custom validation logic. Add it to the ValidationRules collection property of a System.Windows.Data.Binding. Optionally, create a custom control template to override the default Validation. ErrorTemplate, and change the appearance of a UI element when its data is invalid.

How It Works

The ValidationRules collection of a Binding provides a way to check the data passed into a binding and mark it as invalid if it fails any of the rules. The ValidationRule class has a method called Validate, which can be overridden to provide the custom validation logic. The Validate method takes the data passed into the binding as a parameter and returns an instance of the System.Windows.Controls.ValidationResult class. The IsValid property of the ValidationResult specifies whether the rule has passed or failed. Set this property to False if the data should be marked as invalid. The ValidationResult class also has an ErrorContent property, which can be used to inform the user as to why the data is invalid. In the following example, this property is used to set an error message that is then displayed in a System.Windows.FrameworkElement.ToolTip control.

In the XAML for a UI element, declare a Binding object as a nested element, instead of as an inline attribute. This allows you to add rules to the Binding's ValidationRules property.

By default, when a binding for a UI element is invalid, its control template is altered so that it is displayed with a thin red border around it. However, you can create a custom System. Windows.Style that assigns a different control template to the Validation.ErrorTemplate attached property on the target element. This allows you to give a control a more sophisticated appearance when its data is invalid. It also allows you to give feedback to the user by exposing the ErrorContent value of the ValidationResult.

The Code

The following example demonstrates a window containing a System.Windows.Controls.Slider control and a System.Windows.Controls.TextBlock control. The XAML statement for the Text property of the TextBlock specifies a Binding statement that binds it to the Value property of the Slider control. In the binding statement, the Mode attribute is set to TwoWay and the UpdateSourceTrigger attribute to PropertyChanged. This ensures that when a number from 1 to 100 is typed into the TextBox, the Slider control immediately updates its value to reflect it.

In the Binding declaration, a local ValidationRule called PercentageRule is added to the ValidationRules collection. This class checks that the value entered into the TextBox is a number between 0 and 100. If it is not, the ValidationRule returns a ValidationResult with the IsValid property set to False and an ErrorContent that states "Must be a number between 0 and 100."

The TextBox is assigned a Style called textBoxInErrorStyle, which is declared in the window's Resources collection. This Style does two things. First, it ensures that when the attached property Validation.HasError is set to True, it assigns the value of the ErrorContent to the ToolTip property of the TextBox. Second, it assigns a new control template to the Validation. ErrorTemplate property. This ensures that when the TextBox is invalid, an error icon is shown to the right of it, and if the user hovers over this icon with the mouse, the error description is displayed in its ToolTip.

The XAML for the window is as follows:

<Window
    x:Class="Recipe_05_15.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Recipe_05_15="clr-namespace:Recipe_05_15"
    Title="WPF Recipes 5_15" Height="100" Width="260">

    <Window.Resources>

        <!-- A TextBox style to replace the default ErrorTemplate. -->
        <-- When the validation rule fails, an error icon is      -->
        <-- shown next to the TextBox, and the error message is   -->
        <-- displayed in the ToolTip.                             -->
<Style
        x:Key="textBoxInErrorStyle"
        TargetType="{x:Type TextBox}" >
        <Style.Triggers>

            <!-- A Property Trigger that sets the value of the  -->
            <!-- Tooltip to the error message, when the binding -->
            <!-- has a validation error.                        -->
            <Trigger
                Property="Validation.HasError"
                Value="true">
                <Setter
                    Property="ToolTip"
                    Value="{Binding
                            RelativeSource={x:Static
                                              RelativeSource.Self},
                            Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
        </Style.Triggers>

        <!-- A Property Setter that sets the ErrorTemplate to -->
        <!-- display an error icon to the right of the TextBox. -->
        <Setter
            Property="Validation.ErrorTemplate">
            <Setter.Value>
                <ControlTemplate>
                    <DockPanel DockPanel.Dock="Right">
                    <AdornedElementPlaceholder/>
                    <Image
                       Source="Error.png"
                       Width="16"
                       Height="16"
                       ToolTip="{Binding
                                 Path=AdornedElement.ToolTip,
                                 RelativeSource={RelativeSource
                                    Mode=FindAncestor,
                                    AncestorType={x:Type Adorner}}}"/>
                </DockPanel>
            </ControlTemplate>
        </Setter.Value>
     </Setter>
   </Style>
</Window.Resources>

<StackPanel>
<!-- A Slider control that displays a value from 0 to 100 -->
<Slider Name="slider"
        Margin="4" Interval="1"
        TickFrequency="1"
        IsSnapToTickEnabled="True"
        Minimum="0" Maximum="100"/>

<StackPanel Orientation="Horizontal" >
    <TextBlock
        Width="Auto" Margin="4"
        HorizontalAlignment="Left"
        VerticalAlignment="Center"
        Text="Gets and sets the value of the slider:" />

            <!-- A TextBox with a two-way binding between its Text property -->
            <!-- and the Slider control's Value property. The -->
            <!-- textBoxInErrorStyle resource is assigned as its Style property. -->
            <TextBox
                Width="40" Margin="4"
                Style="{StaticResource textBoxInErrorStyle}"
                HorizontalAlignment="Center" >
              <TextBox.Text>
                    <Binding
                        ElementName="slider"
                        Path="Value"
                        Mode="TwoWay"
                        UpdateSourceTrigger="PropertyChanged" >

                        <!-- Adds a ValidationRule, specifiying -->
                        <!-- the local PercentageRule class. -->
                        <Binding.ValidationRules>
                            <Recipe_05_15:PercentageRule/>
                        </Binding.ValidationRules>
                    </Binding>
                </TextBox.Text>
            </TextBox>
        </StackPanel>
    </StackPanel>
</Window>

The code for the PercentageRule class is as follows:

using System.Globalization;
using System.Windows.Controls;
namespace Recipe_05_15
{
    /// <summary>
    /// ValidationRule class to validate that a value is a
    /// number from 0 to 100.
    /// </summary>
    public class PercentageRule : ValidationRule
    {
        // Override the Validate method to add custom validation logic
        //
        public override ValidationResult Validate(
            object value,
            CultureInfo cultureInfo)
        {
            string stringValue = value as string;

            // Check whether there is a value
            if(!string.IsNullOrEmpty(stringValue))
            {
                // Check whether the value can be converted to a double
                double doubleValue;
                if(double.TryParse(stringValue, out doubleValue))
                {
                    // Check whether the double is between 0 and 100
                    if(doubleValue >= 0 && doubleValue <= 100)
                    {
                        // Return a ValidationResult with the IsValid
                        // property set to True
                        return new ValidationResult(true, null);
                    }
                }
            }
            // Return a ValidationResult with the IsValid
            // property set to False. Also specify an error message,
            // which will be displayed in the ToolTip.
            return
                new ValidationResult(
                    false, "Must be a number between 0 and 100");
        }
    }
}

Figure 5-16 shows the resulting window.

Validating a binding.

Figure 5-16. Validating a binding.

Bind to IDataErrorInfo

Problem

You need to bind to a data object that implements the System.ComponentModel.IDataErrorInfo interface and display its error messages when the object is in an invalid state.

Solution

Create a class that implements IDataErrorInfo, and specify custom validation logic that returns error messages when a property value is invalid. Set the ValidatesOnErrors property of a System.Windows.Data.Binding to True. Optionally, create a custom control template to override the default Validation.ErrorTemplate, and change the appearance of a UI element when its data is invalid.

How It Works

The IDataErrorInfo interface is the standard construct in .NET for supporting the validation of CLR objects. It returns error messages for properties that are in an invalid state. Typically, you would implement a business rules engine or validation framework to determine whether any property values are invalid. Alternatively, you can simply embed your validation logic in your classes, as in the following example.

To enable a binding for validation, set the ValidatesOnDataErrors property of your Binding object to True. Internally, this will add a System.Windows.Controls.DataErrorValidationRule to the Binding's ValidationRules collection. The WPF binding system will now interrogate the data source's IDataErrorInfo members for validation errors.

By default, when a binding for a UI element is invalid, its control template is altered so that it is displayed with a thin red border around it. However, you can create a custom System. Windows.Style that assigns a different control template to the Validation.ErrorTemplate attached property on the target element.

The Code

The following example demonstrates a window that displays the name and age data of a Person object using three System.Windows.Controls.TextBox controls. The Person object is assigned to the DataContext property of the window in its constructor. The Person class implements the IDataErrorInfo and contains custom validation logic to check that its properties have valid values. In the binding statements for the controls, the ValidatesOnErrors property is set to True. A custom System.Windows.Style resource is also assigned to the TextBox. This ensures that when the data in the TextBox is invalid, an error icon is displayed to its right, and the ToolTip property displays the error message.

The XAML for the window is as follows:

<Window
    x:Class="Recipe_05_16.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 5_16" Height="116" Width="260">

    <Window.Resources>

        <!-- A TextBox style to replace the default ErrorTemplate. -->
        <!-- When the validation rule fails, an error icon is      -->
        <!-- shown next to the TextBox, and the error message is   -->
        <!-- displayed in the ToolTip.                             -->
        <Style
            x:Key="textBoxInErrorStyle"
            TargetType="{x:Type TextBox}" >
            <Style.Triggers>

                <!-- A Property Trigger that sets the value of the -->
                <!-- Tooltip to the error message, when the binding -->
                <!-- has a validation error.                      -->
                <Trigger
                    Property="Validation.HasError"
                    Value="true">
                    <Setter
                        Property="ToolTip"
                        Value="{Binding
                                RelativeSource={x:Static
                                                  RelativeSource.Self},
                                Path=(Validation.Errors)[0].ErrorContent}"/>
                </Trigger>
            </Style.Triggers>

            <!-- A Property Setter that sets the ErrorTemplate to -->
            <!-- display an error icon to the right of the TextBox. -->
            <Setter
                Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate>
                        <DockPanel DockPanel.Dock="Right">
<AdornedElementPlaceholder/>
                        <Image
                           Source="Error.png"
                           Width="16"
                           Height="16"
                           ToolTip="{Binding
                                    Path=AdornedElement.ToolTip,
                                    RelativeSource={RelativeSource
                                    Mode=FindAncestor,
                                    AncestorType={x:Type Adorner}}}"/>
                    </DockPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</Window.Resources>

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="74"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="14"/>
    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>
        <RowDefinition Height="26"/>
        <RowDefinition Height="26"/>
        <RowDefinition Height="26"/>
    </Grid.RowDefinitions>

    <TextBlock
        Margin="4"
        Text="First Name"
        VerticalAlignment="Center"/>
    <TextBox
        Style="{StaticResource textBoxInErrorStyle}"
        Text="{Binding Path=FirstName,
                       Mode=TwoWay,
                       UpdateSourceTrigger=PropertyChanged,
                       ValidatesOnDataErrors=True}"
        Margin="4" Grid.Column="1"/>

    <TextBlock
        Margin="4"
        Text="Last Name"
Grid.Row="1"
        VerticalAlignment="Center"/>
    <TextBox
        Margin="4"
        Style="{StaticResource textBoxInErrorStyle}"
        Text="{Binding Path=LastName,
                       Mode=TwoWay,
                       UpdateSourceTrigger=PropertyChanged,
                       ValidatesOnDataErrors=True}"
        Grid.Column="1" Grid.Row="1"/>

    <TextBlock
        Margin="4"
        Text="Age"
        Grid.Row="2"
        VerticalAlignment="Center"/>
    <TextBox
        Style="{StaticResource textBoxInErrorStyle}"
        Margin="4"
        Text="{Binding Path=Age,
                       Mode=TwoWay,
                       UpdateSourceTrigger=PropertyChanged,
                       ValidatesOnDataErrors=True}"
        Grid.Column="1"
        Grid.Row="2"/>

    </Grid>
</Window>

The code-behind for the window is as follows:

using System.Windows;
using Recipe_05_16;

namespace Recipe_05_16
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            // Set the DataContext to a Person object
            this.DataContext =
                new Person()
{
                    FirstName = "Elin",
                    LastName = "Binkles",
                    Age = 26,
                };
        }
    }
}

The code for the Person class is as follows:

using System.ComponentModel;

namespace Recipe_05_16
{
    public class Person
        : INotifyPropertyChanged,
          IDataErrorInfo
    {
        private string firstName;
        private string lastName;
        private int age;

        public Person()
        {
            FirstName = "spod";
        }
        public string FirstName
        {
            get
            {
                return firstName;
            }
            set
            {
                if(firstName != value)
                {
                    firstName = value;
                    OnPropertyChanged("FirstName");
                }
            }
        }

        public string LastName
        {
            get
{
                return lastName;
            }
            set
            {
                if(this.lastName != value)
                {
                    this.lastName = value;
                    OnPropertyChanged("LastName");
                }
            }
        }

        public int Age
        {
            get
            {
                return age;
            }
            set
            {
                if(this.age != value)
                {
                    this.age = value;
                    OnPropertyChanged("Age");
                }
            }
        }

        #region INotifyPropertyChanged Members

        /// Implement INotifyPropertyChanged to notify the binding
        /// targets when the values of properties change.
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(
            string propertyName)
        {
            if(this.PropertyChanged != null)
            {
                // Raise the PropertyChanged event
                this.PropertyChanged(
                    this,
                    new PropertyChangedEventArgs(
                        propertyName));
            }
        }
#endregion

        #region IDataErrorInfo Members

        // Implement IDataErrorInfo to return custom
        // error messages when a property value
        // is invalid.

        public string Error
        {
            get
            {
                return string.Empty;
            }
        }

        public string this[string propertyName]
        {
            get
            {
                // Return an empty string if there are no errors
                string message = string.Empty;

                switch(propertyName)
                {
                    case "FirstName":
                        if(string.IsNullOrEmpty(firstName))
                            message = "A person must have a first name.";
                        break;

                    case "LastName":
                        if(string.IsNullOrEmpty(lastName))
                            message = "A person must have a last name.";
                        break;

                    case "Age":
                        if(age < 1)
                            message = "A person must have an age.";
                        break;

                    case "Occupation":
                        if(string.IsNullOrEmpty(firstName))
                            message = "A person must have an occupation.";
                        break;
                    default:
                        break;
                }
return message;
            }
        }

        #endregion
    }
}

Figure 5-17 shows the resulting window. If you delete the values in any of the TextBox controls, the error icon is displayed, and the tool tip shows the error message.

Binding to IDataErrorInfo

Figure 5-17. Binding to IDataErrorInfo

Bind to a Collection with the Master-Detail Pattern

Problem

You need to bind to the items in a data collection and display more information about the selected item. For example, you might display a list of product names and prices on one side of the screen and a more detailed view of the selected product on the other side.

Solution

Bind a data collection to the ItemsSource property of a System.Windows.Controls.ItemsControl such as a System.Windows.Controls.ListBox, System.Windows.Controls.ListView, or System.Windows.Controls.TreeView. Implement the System.Collections.Specialized.INotifyCollectionChanged on the data collection to ensure that insertions or deletions in the collection update the UI automatically. Implement the master-detail pattern by binding a System.Windows.Controls.ContentControl to the same collection.

How It Works

To bind an ItemsControl to a collection object, set its ItemsSource property to an instance of a collection class. This is a class that implements the System.Collections.IEnumerable interface, such as System.Collections.Generic.List<T> or System.Collections.ObjectModel.Collection<T>, or the System.Collections.IList and System.Collections.ICollection interfaces. However, if you bind to any of these objects, the binding will be one-way and read-only. To set up dynamic bindings so that insertions or deletions in the collection update the UI automatically, the collection must implement the System.Collections.Specialized.INotifyCollectionChanged interface. This interface provides the mechanism for notifying the binding target of changes to the source collection, in much the same way as the System.ComponentModel.INotifyPropertyChanged interface notifies bindings of changes to properties in single objects.

INotifyCollectionChanged exposes an event called CollectionChanged that should be raised whenever the underlying collection changes. When you raise this event, you pass in an instance of the System.Collections.Specialized.NotifyCollectionChangedEventArgs class. This contains properties that specify the action that caused the event, for example, whether items were added, moved, or removed from the collection and the list of affected items. The binding mechanism listens for these events and updates the target UI element accordingly.

You do not need to implement INotifyCollectionChanged on your own collection classes. WPF provides the System.Collections.ObjectModel.ObservableCollection<T> class, which is a built-in implementation of a data collection that exposes INotifyCollectionChanged. If your collection classes are instances of the ObservableCollection<T> class or they inherit from it, you will get two-way dynamic data binding for free.

Note

To fully support transferring data values from source objects to targets, each object in your collection that supports bindable properties must also implement the INotifyPropertyChanged interface. It is common practice to create a base class for all your custom business objects that implements INotifyPropertyChanged and a base collection class for collections of these objects that inherits from ObservableCollection<T>. This automatically enables all your custom objects and collection classes for data binding.

To implement the master-detail scenario of binding to a collection, you simply need to bind two or more controls to the same System.Windows.Data.CollectionView object. A CollectionView represents a wrapper around a binding source collection that allows you to navigate, sort, filter, and group the collection, without having to manipulate the underlying source collection itself. When you bind to any class that implements IEnumerable, the WPF binding engine creates a default CollectionView object automatically behind the scenes. So if you bind two or more controls to the same ObservableCollection<T> object, you are in effect binding them to the same default CollectionView class. If you want to implement custom sorting, grouping, and filtering of your collection, you will need to define a CollectionView explicitly yourself. You do this by creating a System.Windows.Data.CollectionViewSource class in your XAML. This approach is demonstrated in the next few recipes in this chapter. However, for the purpose of implementing the master-detail pattern, you can simply bind directly to an ObservableCollection<T> and accept the default CollectionView behind the scenes.

To display the master aspect of the pattern, simply bind your collection to the ItemsSource property of an ItemsControl, such as a System.Windows.Controls.ListBox, System.Windows. Controls.ListView, or System.Windows.Controls.TreeView. If you do not specify a DataTemplate for the ItemTemplate property of the ItemsControl, you can use the DisplayMemberPath property to specify the name of the property the ItemsControl should display. If you do not support a value for DisplayMemberPath, it will display the value returned by the ToString method of each data item in the collection.

To display the detail aspect of the pattern for the selected item, simply bind a singleton object to the collection, such as a ContentControl. When a singleton object is bound to a CollectionView, it automatically binds to the CurrentItem of the view.

If you are explicitly creating a CollectionView using a CollectionViewSource object, it will automatically synchronize currency and selection between the binding source and targets. However, if you are bound directly to an ObservableCollection<T> or other such IEnumerable object, then you will need to set the IsSynchronizedWithCurrentItem property of your ListBox to True for this to work. Setting the IsSynchronizedWithCurrentItem property to True ensures that the item selected always corresponds to the CurrentItem property in the ItemCollection. For example, suppose there are two ListBox controls with their ItemsSource property bound to the same ObservableCollection<T>. If you set IsSynchronizedWithCurrentItem to True on both ListBox controls, the selected item in each is the same.

The Code

The following example demonstrates a window that data binds to an instance of the PersonCollection class in its constructor. The PersonCollection class is an ObservableCollection<T> of Person objects. Each Person object exposes name, age, and occupation data, as well as a description.

In the top half of the window, a ListBox is bound to the window's DataContext. This is assigned an instance of the PersonCollection in the code-behind for the window. The ItemTemplate property of the ListBox references a DataTemplate called masterTemplate defined in the window's Resources collection. This shows the value of the Description property for each Person object in the collection. It sets the UpdateSourceTrigger attribute to System.Windows.Data. UpdateSourceTrigger.PropertyChanged. This ensures that the text in the ListBox item is updated automatically and immediately when the Description property of a Person changes. In the bottom half of the window, a ContentControl binds to the same collection. Because it is a singleton UI element and does not display a collection of items, it automatically binds to the current item in the PersonCollection class. Because the IsSynchronizedWithCurrentItem property of the ListBox is set to True, this corresponds to the selected item in the ListBox. The ContentControl uses a DataTemplate called detailTemplate to display the full details of the selected Person.

When the data displayed in the details section is changed, it automatically updates the corresponding description in the master section above it. This is made possible for two reasons. First, the System.Windows.Controls.TextBox controls in the details section specify a System. Windows.Data.Binding.BindingMode of TwoWay, which means that when new text is input, it is automatically marshaled to the binding source. Second, the Person class implements the INotifyPropertyChanged interface. This means that when a value of a property changes, the binding target is automatically notified.

At the bottom of the window, there is a System.Windows.Controls.Button control marked Add Person. When this button is clicked, it adds a new Person object to the collection. Because the PersonCollection class derives from ObservableCollection<T>, which in turn implements INotifyCollectionChanged, the master list of items automatically updates to show the new item.

The XAML for the window is as follows:

<Window x:Class="Recipe_05_17.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Recipes 5_17" Height="370" Width="280">
    <Window.Resources>

        <DataTemplate
            x:Key="masterTemplate">
            <TextBlock
                Margin="4"
                Text="{Binding
                       Path=Description,
                       UpdateSourceTrigger=PropertyChanged}"/>
        </DataTemplate>

        <DataTemplate x:Key="detailTemplate">
            <Border
                BorderBrush="LightBlue"
                BorderThickness="1">
                <Grid Margin="10">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="74"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>

                    <Grid.RowDefinitions>
                        <RowDefinition Height="26"/>
                        <RowDefinition Height="26"/>
                        <RowDefinition Height="26"/>
                        <RowDefinition Height="26"/>
                    </Grid.RowDefinitions>

                    <TextBlock
            Margin="4"
            Text="First Name"
            VerticalAlignment="Center"/>
        <TextBox
            Text="{Binding Path=FirstName, Mode=TwoWay}"
            Margin="4" Grid.Column="1"/>

        <TextBlock
            Margin="4"
            Text="Last Name"
            Grid.Row="1"
            VerticalAlignment="Center"/>
<TextBox
            Margin="4"
            Text="{Binding Path=LastName, Mode=TwoWay}"
            Grid.Column="1" Grid.Row="1"/>

        <TextBlock
            Margin="4"
            Text="Age"
            Grid.Row="2"
            VerticalAlignment="Center"/>
        <TextBox
            Margin="4"
            Text="{Binding Path=Age, Mode=TwoWay}"
            Grid.Column="1"
            Grid.Row="2"/>

        <TextBlock
            Margin="4"
            Text="Occupation"
            Grid.Row="3"
            VerticalAlignment="Center"/>

        <ComboBox
            x:Name="cboOccupation"
            IsEditable="False"
            Grid.Column="1"
            Grid.Row="3"
            HorizontalAlignment="Left"
            Text="{Binding Path=Occupation, Mode=TwoWay}"
            Margin="4" Width="140">
             <ComboBoxItem>Student</ComboBoxItem>
             <ComboBoxItem>Engineer</ComboBoxItem>
             <ComboBoxItem>Professional</ComboBoxItem>
        </ComboBox>

    </Grid>
            </Border>
         </DataTemplate>
    </Window.Resources>

    <StackPanel Margin="5">

        <TextBlock
            VerticalAlignment="Center"
            FontSize="14"
            Margin="4"
            Text="People"/>
<!-- The ItemsControls binds to the collection. -->
        <ListBox
            ItemsSource="{Binding}"
            ItemTemplate="{StaticResource masterTemplate}"
            IsSynchronizedWithCurrentItem="True" />

        <TextBlock
            VerticalAlignment="Center"
            FontSize="14"
            Margin="4"
            Text="Details"/>

        <!-- The ContentControl binds to the CurrentItem of the collection. -->
        <ContentControl
          Content="{Binding}"
          ContentTemplate="{StaticResource detailTemplate}" />

        <!-- Add a new person to the collection. -->
        <Button
            Margin="4"
            Width="100"
            Height="34"
            HorizontalAlignment="Right"
            Click="AddButton_Click">
            Add Person
        </Button>
    </StackPanel>
</Window>

The code-behind for the window is as follows:

using System.Windows;
using Recipe_05_17;

namespace Recipe_05_17
{
   public partial class Window1 : Window
   {
       // Create an instance of the PersonCollection class
       PersonCollection people =
           new PersonCollection();

       public Window1()
       {
           InitializeComponent();

           // Set the DataContext to the PersonCollection
           this.DataContext = people;
       }
private void AddButton_Click(
           object sender, RoutedEventArgs e)
       {
           people.Add(new Person()
                          {
                              FirstName = "Nelly",
                              LastName = "Bonks",
                              Age = 26,
                              Occupation = "Professional"
                          });
       }
   }
}

The code for the Person class is omitted for brevity. It is identical to the Person class used in recipe 5-4, so you can see the full code in that recipe. The code for the PersonCollection class is as follows:

using System.Collections.ObjectModel;
using Recipe_05_17;

namespace Recipe_05_17
{

   public class PersonCollection
       : ObservableCollection<Person>
   {
       public PersonCollection()
       {
           // Load the collection with dummy data
           //
           Add(new Person(){FirstName = "Elin",
                            LastName = "Binkles",
                            Age = 26,
                            Occupation = "Professional"});

           Add(new Person(){FirstName = "Samuel",
                            LastName = "Bourts",
                            Age = 28,
                            Occupation = "Engineer"});

           Add(new Person(){FirstName = "Alan",
                            LastName = "Jonesy",
                            Age = 37,
                            Occupation = "Engineer"});
Add(new Person(){FirstName = "Sam",
                            LastName = "Nobles",
                            Age = 25,
                            Occupation = "Engineer"});
       }
   }
}

Figure 5-18 shows the resulting window.

Binding to a collection using the master-detail pattern

Figure 5-18. Binding to a collection using the master-detail pattern

Sort Data in a Collection

Problem

You need to sort a collection of items based on the value of a property.

Solution

Create a System.Windows.Data.CollectionViewSource as a static resource, and bind it to the data collection. Specify a System.ComponentModel.SortDescription using the name of the property you want to sort on.

How It Works

A CollectionViewSource is a layer on top of the binding source collection that allows you to expose a custom System.Windows.Data.CollectionView class for your data. A CollectionView represents a view of the items in a data collection and can supply custom grouping, sorting, filtering, and navigation.

To specify how the items in the collection view are sorted, create a System.ComponentModel.SortDescription object, and add it to the CollectionViewSource's SortDescriptions collection. A SortDescription defines the direction and the property name to be used as the criteria for sorting the data.

The Code

The following example creates a CollectionViewSource as a static resource that binds to a collection of countries. The CollectionViewSource has a SortDescription property that sorts the data according to each item's Name property. A System.Windows.Controls.ItemsControl binds to the CollectionViewSource and shows the sorted collection.

The XAML for the window is as follows:

<Window x:Class="Recipe_05_18.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ComponentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
    xmlns:local="clr-namespace:Recipe_05_18"
    Title="WPF Recipes 5_18" Height="244" Width="124">
    <Window.Resources>

        <!-- Create an instance of the collection class -->
        <local:Countries x:Key="countries"/>

        <!-- Wrap it in a CollectionViewSource -->
        <CollectionViewSource
            x:Key="cvs"
            Source="{Binding
                     Source={StaticResource countries}}">

            <!-- Add a SortDescription to sort by the Name -->
            <CollectionViewSource.SortDescriptions>
                <ComponentModel:SortDescription
                                PropertyName="Name" />
            </CollectionViewSource.SortDescriptions>
        </CollectionViewSource>

  </Window.Resources>

  <Grid>
      <!-- Bind an ItemsControl to the CollectionViewSource -->
      <!-- Set its DisplayMemberPath to display the Name property -->
<ItemsControl
        ItemsSource="{Binding
                      Source={StaticResource cvs}}"
            DisplayMemberPath="Name" />
    </Grid>

</Window>

The code for the data collection and data object is omitted for brevity. Figure 5-19 shows the resulting window.

Sorting a collection

Figure 5-19. Sorting a collection

Apply Custom Sorting Logic to a Collection

Problem

You need to sort a collection of data items based on custom sorting logic.

Solution

Create a custom class that implements the System.Collections.IComparer interface. Add the custom sorting logic to the Compare method to sort the collection of data items based on your custom sort criteria. Use the static GetDefaultView method of the System.Windows.Data.CollectionViewSource class to get the default view your collection. Set the CustomSort property of this view to an instance of your IComparer class.

How It Works

When you bind to a collection class, the WPF binding system creates a default System.Windows. Data.CollectionView behind the scenes. Internally, this wraps your collection and exposes it as a binding source. There is a static method on the CollectionViewSource class called GetDefaultView. This gets the default collection view from your collection. This will be an instance of the System. Windows.Data.ListCollectionView class if your data collection is a System.Collections.IList.

Once you have your ListCollectionView object, you can set its CustomSort property to a class that implements IComparer. This interface exposes a method that compares two objects. Add custom logic to this method to sort your data collection.

The Code

The following example demonstrates a window that creates a System.Collections.ObjectModel. ObservableCollection<T> of strings called SortableCountries as a static resource. The collection contains names of countries prefixed by a number and is displayed in a System.Windows. Controls.ItemsControl. Using the normal SortDescription property of a CollectionViewSource to sort the countries would result in all those beginning with a 1 being before the others. For example, "14 USA" would be above "4 China." In the code-behind for the window, there is an implementation of IComparer called SortCountries. When the System.Windows.Controls.Button control marked Sort is clicked, there is code in the event handler to get the default view from the collection and set an instance of this SortCountries class to the CustomSort property.

The XAML for the window is as follows:

<Window
    x:Class="Recipe_05_19.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Recipe_05_19"
    Title="WPF Recipes 5_19" Height="300" Width="180">

    <Window.Resources>

        <!-- Create an instance of the collection class -->
        <local:SortableCountries x:Key="sortableCountries"/>

    </Window.Resources>

    <Grid Margin="16">
        <StackPanel>

            <ItemsControl
                ItemsSource="{StaticResource sortableCountries}" />

            <Button
                Click="SortButton_Click"
                Content="Sort"
                Margin="8" />

        </StackPanel>

    </Grid>

</Window>

In the SortCountries implementation of IComparer, there is custom logic to sort the numeric prefixes as numbers, not as strings. The full code for this comparison logic is omitted for brevity. However, you can find the code to apply the IComparer in the following code-behind for the window:

using System;
using System.Collections;
using System.Windows;
using System.Windows.Data;
using Recipe_05_19;

namespace Recipe_05_19
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void SortButton_Click(
            object sender, RoutedEventArgs args)
        {
            // Get the ObservableCollection from the window Resources
            SortableCountries sortableCountries =
                (SortableCountries)
                (this.Resources["sortableCountries"]);

            // Get the Default View from the ObservableCollection
            ListCollectionView lcv =
                (ListCollectionView)
                CollectionViewSource.GetDefaultView(sortableCountries);

            // Set the Custom Sort class
            lcv.CustomSort = new SortCountries();
        }
   }

   public class SortCountries
       : IComparer
   {
       public int Compare(object x, object y)
       {
           // Custom sorting logic goes here.
           // (Omitted for brevity).
           //
           string stringX = x.ToString();
           string stringY = y.ToString();
int ret = 0;

           // [...]

           return ret;
        }
    }
}

Figure 5-20 shows the difference between the two lists, before and after the custom sorting logic is applied.

Applying custom sorting logic

Figure 5-20. Applying custom sorting logic

Filter Data in a Collection

Problem

You need to filter a collection of items based on a value of a property.

Solution

Create a System.Windows.Data.CollectionViewSource as a static resource, and bind it to the data collection. Set the Filter property of the CollectionViewSource to a System.Windows. Data.FilterEventHandler. In the code for this event handler, add custom logic to determine which items in the collection should be displayed.

How It Works

A CollectionViewSource wraps a binding source collection and allows you to expose a custom view of its data based on sort, filter, and group queries. When a FilterEventHandler is assigned to its Filter property, the event handler is called for each item in the collection. The event handler takes an instance of the System.Windows.Data.FilterEventArgs class as its event argument. If a data item should be included in the collection view, set the Accepted property of the FilterEventArgs to True. If it should not pass through the filter, simply set the Accepted property to False.

The Code

The following example creates a CollectionViewSource as a static resource that binds to a collection of countries. The CollectionViewSource has a Filter property that references an EventHandler called CollectionViewSource_EuropeFilter in the code-behind for the window. This event handler filters out countries in the collection that are not in Europe.

The XAML for the window is as follows:

<Window
    x:Class="Recipe_05_20.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Recipe_05_20"
    Title="WPF Recipes 5_20" Height="124" Width="124">
    <Window.Resources>

        <!-- Create an instance of the collection class -->
        <local:Countries x:Key="countries"/>

        <!-- Wrap it in a CollectionViewSource -->
        <!-- Set the Filter property to a FilterEventHandler -->
        <CollectionViewSource
            x:Key="cvs"
            Source="{Binding
                     Source={StaticResource countries}}"
            Filter="CollectionViewSource_EuropeFilter" />

    </Window.Resources>

    <Grid>
        <!-- Bind an ItemsControl to the CollectionViewSource -->
        <!-- Set its DisplayMemberPath to display the Name property -->
        <ItemsControl
            ItemsSource="{Binding
                          Source={StaticResource cvs}}"
            DisplayMemberPath="Name"/>
    </Grid>
</Window>

The code-behind for the window is as follows:

using System.Windows;
using System.Windows.Data;
using Recipe_05_20;


namespace Recipe_05_20
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        // Filter the collection of countries.
        private void CollectionViewSource_EuropeFilter(
            object sender, FilterEventArgs e)
        {
            // Get the data item
            Country country = e.Item as Country;

            // Accept it into the collection view, if its
            // Continent property equals Europe.
            e.Accepted = (country.Continent == Continent.Europe);
        }
    }
}

The data for the collection and its data items is as follows:

using System.Collections.ObjectModel;


namespace Recipe_05_20
{
    public class Country
    {
        private string name;
        private Continent continent;

        public string Name
        {
            get{ return name;}
            set{name = value;}
        }
public Continent Continent
        {
            get{return continent;}
            set{continent = value;}
        }

        public Country(string name, Continent continent)
        {
            this.name = name;
            this.continent = continent;
        }
   }

   public enum Continent
   {
       Asia,
       Africa,
       Europe,
       NorthAmerica,
       SouthAmerica,
       Australasia
   }

   public class Countries : Collection<Country>
   {
       public Countries()
       {
           this.Add(new Country("Great Britan", Continent.Europe));
           this.Add(new Country("USA", Continent.NorthAmerica));
           this.Add(new Country("Canada", Continent.NorthAmerica));
           this.Add(new Country("France", Continent.Europe));
           this.Add(new Country("Germany", Continent.Europe));
           this.Add(new Country("Italy", Continent.Europe));
           this.Add(new Country("Spain", Continent.Europe));
           this.Add(new Country("Brazil", Continent.SouthAmerica));
           this.Add(new Country("Argentina", Continent.SouthAmerica));
           this.Add(new Country("China", Continent.Asia));
           this.Add(new Country("India", Continent.Asia));
           this.Add(new Country("Japan", Continent.Asia));
           this.Add(new Country("South Africa", Continent.Africa));
           this.Add(new Country("Tunisia", Continent.Africa));
           this.Add(new Country("Egypt", Continent.Africa));
       }
   }
}

Figure 5-21 shows the resulting window.

Filtering a collection

Figure 5-21. Filtering a collection

Group Data in a Collection

Problem

You need to group a collection of items based on a value of a property.

Solution

Use a System.Windows.Data.CollectionViewSource to wrap a collection and group its items, and create a System.Windows.Controls.GroupStyle to control how the group headers are displayed.

How It Works

A CollectionViewSource is a layer on top of the binding source collection that allows you to expose a custom view of the collection based on sort, filter, and group queries.

Create the CollectionViewSource as a static resource in the System.Windows.ResourceDictionary for your window, and bind it to the collection you want to group. Add a System.Windows.Data.PropertyGroupDescription to the GroupDescriptions collection property of the CollectionViewSource, and specify the name of the property you want to group the items by. Use the GroupStyle property of the System.Windows.Controls.ItemsControl to specify a HeaderTemplate to use for the group headers.

The Code

The following example creates a CollectionViewSource as a static resource that binds to a collection of countries. The Country class has two properties, Name and Continent, and the CollectionViewSource uses a PropertyGroupDescription to group the countries according to the value of their Continent property. A System.Windows.Controls.ItemsControl binds to the CollectionViewSource and shows the grouped collection. It declares a GroupStyle that references a System.Windows.DataTemplate called groupingHeaderTemplate. This DataTemplate defines the display style for group headers.

The XAML for the window is as follows:

<Window x:Class="Recipe_05_21.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:Recipe_05_21="clr-namespace:Recipe_05_21"
     Title="WPF Recipes 5_21" Height="294" Width="160">
    <Window.Resources>

        <!-- Create an instance of the collection class -->
        <Recipe_05_21:Countries x:Key="countries"/>

        <!-- Wrap it in a CollectionViewSource -->
        <CollectionViewSource
            x:Key="cvs"
            Source="{Binding
                     Source={StaticResource countries}}">

            <!-- Add a PropertyGroupDescription to group by the Continent -->
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="Continent"/>
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>

        <!-- DataTemplate to display the group header -->
        <DataTemplate x:Key="groupingHeaderTemplate">
            <Border Height="28">
                <Label VerticalAlignment="Center" Content="{Binding}"
                       BorderBrush="#FF8F8D8D" BorderThickness="0,0,0,0.5"
                       Foreground="#FF666666">
                <Label.Background>
                    <LinearGradientBrush
                        EndPoint="0.506,-0.143" StartPoint="0.502,11.643">
                        <GradientStop Color="#FF000000" Offset="0"/>
                        <GradientStop Color="#FFFFFFFF" Offset="1"/>
                    </LinearGradientBrush>
                </Label.Background>
            </Label>
         </Border>
      </DataTemplate>

   </Window.Resources>

   <Grid>
       <!-- Bind an ItemsControl to the CollectionViewSource -->
       <!-- Set its DisplayMemberPath to display the Name property -->
<ItemsControl
           ItemsSource="{Binding
                        Source={StaticResource cvs}}"
           DisplayMemberPath="Name">

           <!-- Create a GroupStyle that uses the DataTemplate -->
           <ItemsControl.GroupStyle>
               <GroupStyle HeaderTemplate=
                           "{StaticResource groupingHeaderTemplate}" />
           </ItemsControl.GroupStyle>
       </ItemsControl>
   </Grid>
</Window>

Figure 5-22 shows the resulting window.

Grouping a collection

Figure 5-22. Grouping a collection

Apply Custom Grouping to a Collection

Problem

You need to group a collection of items based on custom logic, not just on a value of a property.

Solution

Create a System.Windows.Data.CollectionViewSource as a static resource, and bind it to the collection. Create a class that implements the System.Windows.Data.IValueConverter interface and contains the custom grouping logic. Declare the IValueConverter implementation as a static resource. Add a PropertyGroupDescription to the GroupDescriptions collection property of the CollectionViewSource, and specify the Converter property. Use the GroupStyle property of the System.Windows.Controls.ItemsControl to specify the default GroupStyle.

How It Works

When the CollectionViewSource is bound to the collection, the IValueConverter.Convert method is invoked for each item in the collection. This contains custom logic in the code-behind for deciding to which group each item belongs.

The Code

The following example creates a CollectionViewSource as a static resource that binds to a collection of countries. It also declares an IValueConverter class as a static resource, which is defined in the code-behind for the window and which contains the code to divide the countries into two groups. The resulting grouped data collection is displayed in an ItemsControl.

Note

If you don't create a custom DataTemplate to define the display of your groups' headers, you have to specify the default GroupStyle. This indents the items in a group. For more sophisticated visualizations, create a DataTemplate to display a group header, and specify it as the HeaderTemplate property of your ItemsControl's GroupStyle.

The XAML for the window is as follows:

<Window
    x:Class="Recipe_05_22.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Recipe_05_22="clr-namespace:Recipe_05_22"
    Title="WPF Recipes 5_22" Height="294" Width="160">

    <Window.Resources>

        <!-- Create an instance of the collection class -->
        <Recipe_05_22:Countries x:Key="countries"/>

        <!-- Create an instance of the GroupByContinentConverter class -->
        <Recipe_05_22:GroupByContinentConverter
            x:Key="GroupByContinentConverter"/>

        <!-- Wrap the collection in a CollectionViewSource -->
        <!-- Set the Filter property to a FilterEventHandler -->
        <CollectionViewSource
            x:Key="cvs"
            Source="{Binding
                     Source={StaticResource countries}}">
<!-- Add a PropertyGroupDescription that uses -->
            <!-- the GroupByContinentConverter class to create the groups -->
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription
                    Converter="{StaticResource GroupByContinentConverter}" />
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>

    </Window.Resources>

    <Grid>
        <!-- Bind an ItemsControl to the CollectionViewSource. -->
        <!-- Set its DisplayMemberPath to display the Name property. -->
        <!-- Set the GroupStyle to use the Default. -->
        <ItemsControl
            Margin="10"
             ItemsSource="{Binding Source={StaticResource cvs}}"
             DisplayMemberPath="Name" >

            <!-- The default GroupStyle indents the items in a group -->
            <ItemsControl.GroupStyle>
                <x:Static Member="GroupStyle.Default"/>
            </ItemsControl.GroupStyle>
        </ItemsControl>

    </Grid>
</Window>

The code in the IValueConverter checks the Continent property of each country and decides whether it should be in the "Americas" or the "Rest of the World" group:

using System;
using System.Globalization;
using System.Windows.Data;
using Recipe_05_22;

namespace Recipe_05_22
{
    public class GroupByContinentConverter
        : IValueConverter
    {
        public object Convert(object value,
                              Type targetType,
                              object parameter,
                              CultureInfo culture)
        {
             Country country = (Country)value;
// Decide which group the country belongs in
             switch (country.Continent)
             {
                 case Continent.NorthAmerica:
                 case Continent.SouthAmerica:
                     return "Americas";

                 default:
                     return "Rest of the World";
             }
        }

        public object ConvertBack(object value,
                                  Type targetType,
                                  object parameter,
                                  CultureInfo culture)
        {
             throw new NotImplementedException();
        }
    }
}

Figure 5-23 shows the resulting window.

Applying custom grouping to a collection

Figure 5-23. Applying custom grouping to a collection

Bind to Application Settings

Problem

You need to bind UI elements to application settings to automatically use and update their values.

Solution

Reference your application's Properties.Settings.Default class as a static binding source in your binding statements.

How It Works

Visual Studio provides a handy mechanism for storing and retrieving application settings dynamically. Using the Settings page of the Project Designer, you can add custom properties and give each one a name, a type, and an initial value. Visual Studio then automatically generates a Settings class and creates standard .NET properties for each setting. It exposes the properties via a static Settings property called Default.

To bind your application settings to UI elements, set the Source property of a System.Windows.Data.Binding to the static Properties.Settings.Default class, and set the Path property to the name of a setting. Set the Mode property to System.Windows.Data.BindingMode.TwoWay to automatically update the application setting when it is changed by your target UI element.

To save changes to your settings, override the OnClosing method of your window, and call the Properties.Savings.Default.Save method.

The Code

The following example demonstrates a window that displays the values of the window's Height, Width, Left, and Top properties. On the project's Settings page, there are four corresponding double properties. Figure 2-24 shows these application settings.

In the XAML for the window, the window's Height, Width, Left, and Top properties are bound to the values of these application settings. The binding statements reference the Properties. Settings.Default class as a static binding source. This ensures that when the window opens, it gets its initial size and position from the application settings.

In the code-behind for the window, the Settings.Default.Save method is called in the OnClosing method. This ensures that when you move or resize the window, these settings are saved and restored the next time the application runs.

Application settings on the Settings page of the Project Designer

Figure 5-24. Application settings on the Settings page of the Project Designer

The XAML for the window is as follows:

<Window
    x:Class="Recipe_05_23.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Properties="clr-namespace:Recipe_05_23.Properties"
    x:Name="MainWindow"
    WindowStartupLocation="Manual"
    Title="WPF Recipes 5_23"
    Height="{Binding
             Source={x:Static
                 Properties:Settings.Default},
             Path=Height,
             Mode=TwoWay}"
    Width="{Binding
            Source={x:Static
                Properties:Settings.Default},
            Path=Width,
            Mode=TwoWay}"
    Left="{Binding
           Source={x:Static
               Properties:Settings.Default},
           Path=Left,
           Mode=TwoWay}"
    Top="{Binding
          Source={x:Static
              Properties:Settings.Default},
Path=Top,
          Mode=TwoWay}" >

    <Grid>
        <Grid VerticalAlignment="Center" HorizontalAlignment="Center">

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="40"/>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="24"/>
            <RowDefinition Height="24"/>
            <RowDefinition Height="24"/>
            <RowDefinition Height="24"/>
        </Grid.RowDefinitions>

        <TextBlock
            Text="Window Height:"/>
        <TextBlock
            Text="{Binding
                   ElementName=MainWindow,
                   Path=Height,
                   UpdateSourceTrigger=PropertyChanged}"
            Grid.Column="1"
            FontWeight="Bold"/>

        <TextBlock
            Text="Window Width:"
            Grid.Row="1"/>
        <TextBlock
            Text="{Binding
                   ElementName=MainWindow,
                   Path=Width,
                   UpdateSourceTrigger=PropertyChanged}"
            Grid.Column="1"
            Grid.Row="1"
            FontWeight="Bold"/>

        <TextBlock
            Text="Window Left:" Grid.Row="2"/>
        <TextBlock
            Text="{Binding
                   ElementName=MainWindow,
                   Path=Left,
                   UpdateSourceTrigger=PropertyChanged}"
Grid.Column="1"
            Grid.Row="2"
            FontWeight="Bold"/>

        <TextBlock
            Text="Window Top:"
            Grid.Row="3"/>
        <TextBlock
            Text="{Binding
                   ElementName=MainWindow,
                   Path=Top,
                   UpdateSourceTrigger=PropertyChanged}"
                 Grid.Column="1"
                 Grid.Row="3"
                 FontWeight="Bold"/>
        </Grid>

    </Grid>
</Window>

The code-behind for the window is as follows:

using System.Windows;
using Recipe_05_23.Properties;

namespace Recipe_05_23
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Override the OnClosing method and save the current settings
        /// </summary>
        /// <param name="e"></param>
        protected override void OnClosing(
            System.ComponentModel.CancelEventArgs e)
        {
            // Save the settings
            Settings.Default.Save();

            base.OnClosing(e);
        }
    }
}

Figure 5-25 shows the resulting window.

Binding to application settings

Figure 5-25. Binding to application settings

Bind to Application Resource Strings

Problem

You need to bind UI elements to application resource strings to automatically use their values in your controls.

Solution

Use the System.Reflection namespace to add the resource strings to the System.Windows.Application instance's System.Windows.ResourceDictionary when your application starts up. Reference the names of your resource strings in your binding statements using the ResourceKey property of the System.Windows.StaticResourceExtension class.

How It Works

You can add resource strings to your application on the Resources page of the Project Designer in Visual Studio. This automatically generates corresponding .NET properties on your application's Resources class. However, because these properties are marked as static, you cannot bind to them directly. Instead, override the OnStartup method of your Application class in the App.xaml.cs file, and use reflection to retrieve all the string properties from the Properties. Resources class. Add these properties and their values to the Application's Resources collection. This makes them available for data binding throughout your application.

To bind the resource strings to your UI elements, use the StaticResource markup extension in your binding statements, and set the value of the ResourceKey property to the name of a resource string.

Note

The resource strings are added to the Application's Resources collection at runtime. This means that when you reference them in your XAML, you may see error messages warning you that the StaticResource reference was not found. However, the project will still compile and run perfectly.

The Code

The following example demonstrates a window that displays two System.Windows.Controls.TextBlock controls and binds them to two resource strings that contain a welcome message and a copyright notice. These resource strings are defined on the project's Resources page, which is shown in Figure 5-26.

Application resource strings on the Resources page of the Project Designer

Figure 5-26. Application resource strings on the Resources page of the Project Designer

In the App.xaml.cs file, the OnStartup method of the Application class is overridden, and the resource strings are added to the resource dictionary. The code-behind for this is as follows:

using System;
using System.Reflection;
using System.Windows;

namespace Recipe_05_24
{
    public partial class App : Application
    {
        /// <summary>
        /// Override the OnStartup method to add the
        /// resource strings to the Application's ResourceDictionary
        /// </summary>
        /// <param name="e"></param>
        protected override void OnStartup(
            StartupEventArgs e)
        {
            // Use reflection to get the PropertyInfo
            // for the Properties.Resources class
            Type resourcesType = typeof(Recipe_05_24.Properties.Resources);
            PropertyInfo[] properties =
                resourcesType.GetProperties(
                    BindingFlags.Static | BindingFlags.NonPublic);
// Add properties to XAML Application.Resources
            foreach(PropertyInfo property in properties)
            {
                // If the property is a string, add it to the
                // application's resources dictionary
                if(property.PropertyType == typeof(string))
                    Resources.Add(
                        property.Name,
                        property.GetValue(null, null));
            }

            base.OnStartup(e);
        }
    }
}

The XAML for the window is as follows:

<Window
    x:Class="Recipe_05_24.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    WindowStartupLocation="Manual"
    Title="WPF Recipes 5_24"
    Height="180" Width="240">

    <StackPanel Margin="10">

        <TextBlock
            Text="{StaticResource
                    ResourceKey=WelcomeMessage}"
            HorizontalAlignment="Center"
            FontSize="16" FontWeight="Bold"/>

        <TextBlock
            Text="{StaticResource
                    ResourceKey=Copyright}"
            HorizontalAlignment="Center"
            Margin="10"
            Grid.Row="1"
            TextWrapping="Wrap"/>

    </StackPanel>
</Window>

Figure 5-27 shows the resulting window.

Binding to application resource strings

Figure 5-27. Binding to application resource strings

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset