Data Binding

Data binding provides a way of connecting your user interface to a source of data, such as an object in your application. This is an elegant means of displaying data in your user interface and enabling the user to update the data and have those changes automatically pushed back into the bound object.

Rather than pushing data into the user interface from the code-behind, XAML's powerful data binding capabilities enable the user interface to pull data in from the appropriate source and populate itself as required.

Silverlight supports a very rich and flexible data binding model, and this data binding model is one of Silverlight's key strengths and one you will find extremely useful when building business applications. In Silverlight, you can bind to objects, resources, and even properties on other controls. As long as a property is a dependency property (dependency properties are a special type of property, discussed in Chapter 12), you can bind it to almost anything. Data binding is implemented using the Binding markup extension.

A data binding requires a source and a target. The source is usually a property on an object, and the target is usually a property on a control. When bound together, the property on the control will be assigned the value of the property on the object.

Figure 2-15 provides a visual interpretation of a binding, connecting the FirstName property of a class named Person (source) to the Text property of a TextBox control (target).

images

Figure 2-15. Visual interpretation of a binding

There are a number of ways to set the source of a data binding. Let's look at those now.

Setting the Source of a Binding

As previously mentioned, you bind a property of a control to a property on an object in XAML using the Binding markup extension. Recall from earlier in this chapter that a markup extension is just a class containing some logic, which is evaluated at runtime and returns a value as a result. When used in XAML, markup extensions are denoted by the curly brackets surrounding them. Before we get into how we specify what source that a binding should use, let's actually set up that source.

Say we have a class called Person in our project, containing the properties FirstName and LastName, matching the fields in our user interface created earlier:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public string FullName
    {
        get { return FirstName + " " + LastName; }
    }
}

This class has to be instantiated as an object, and its properties should be assigned some values before it is consumed by a binding. We could push a Person object into the user interface via some code-behind or use a data source control to obtain it, but for this example, we'll take a XAML-only approach, and instantiate the object as a resource and bind to that. To do so, we must first add a namespace declaration to our XAML file to reference the CLR namespace in which the Person class is defined and assign a prefix that we can use to reference the namespace by (shown in bold):

<UserControl x:Class="Chapter02Workshop.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:my="clr-namespace:Chapter02Workshop"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400" >

Then, we can instantiate the object as a resource in the resources of the UserControl:

<UserControl.Resources>
    <my:Person x:Key="personObject" />
</UserControl.Resources>

images Note You can assign the object initial values if you wish when instantiating the object, in the same manner as you would assign property values on any control in XAML:

<UserControl.Resources>
    <my:Person x:Key="personObject" FirstName="Chris" LastName="Anderson" />
</UserControl.Resources>

Now, we have an object that our bindings can use as a data source. Let's say we have a TextBox control and want to bind its Text property (the target) to the FirstName property on this object (the source). There are two things that we need to set as part of this binding: the object we should use as the data source and the property on that object it should get the value from. The Binding markup extension has a Source property and a Path property, so we can point these properties to the object resource and property name respectively, like so:

<TextBox Text="{Binding Source={StaticResource personObject}, Path=FirstName}" />

One problem with this syntax is that you often have many bindings on a control that use the same data source, and specifying the data source on every binding leads to messy and unmaintainable XAML. The solution to this is to assign the data source to the control's DataContext property. You can omit setting the Source property on the bindings, and the bindings on this control will then use the value assigned to the DataContext property as their data source, unless a binding specifies otherwise.

<TextBox DataContext="{StaticResource personObject}" Text="{Binding Path=FirstName}" />

However, you often have many controls whose bindings share the same data source, and assigning the same data source to the DataContext property on each control still leads to messy and unmaintainable XAML. Fortunately, when you assign a value to the DataContext property on a control, the value will also be inherited down the object hierarchy. This means that any controls below that control will also have the object assigned to their DataContext properties. Let's say all the controls that you wished to bind to the object were in a Grid control. You could, therefore, simply assign the object to the Grid's DataContext property, and all controls within it would inherit its value for their own data context, without requiring you to assign their DataContext properties individually:

<Grid DataContext="{StaticResource personObject}">
    <TextBox Grid.Column="1" Grid.Row="0" Margin="3" Text="{Binding Path=FirstName}" />
    <TextBox Grid.Column="1" Grid.Row="1" Margin="3" Text="{Binding Path=LastName}" />
</Grid>

images Note You may be familiar with data source controls such as the BindingSource control in Windows Forms or the ObjectDataSource control in ASP.NET. These data sources often have the role of obtaining the data to be displayed in the user interface (handling the pulling process), which controls in the view can then bind to. No data source control is included in the standard control library in Silverlight. However, Chapter 5 covers the DomainDataSource control, which is a part of RIA Services, and discusses data source control type behavior.

images Workshop: Binding to an Object

For this example, we'll use the simple user interface created earlier in the previous workshop and walk through instantiating an object (in XAML), using that object as the data source of any bindings on the TextBox controls. We'll then bind the Text property on the TextBox controls to it, which will display the object's property values and enable them to be updated by the user.

  1. Add a new class to your project, named Person. Add the following code to it:
    namespace Chapter02Workshop
    {
        public class Person
        {

            public string FirstName { get; set; }
            public string LastName { get; set; }

            public string FullName
            {
                get { return FirstName + " " + LastName; }
            }
        }
    }
  2. In the MainPage.xaml file we created in the previous example, add a person-Object resource to your project that references the Person class just created, setting some default values for its properties. You should have the following XAML:
    <UserControl x:Class="Chapter02Workshop.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:my="clr-namespace:Chapter02Workshop"
        mc:Ignorable="d"
        d:DesignHeight="300" d:DesignWidth="400" >
        <UserControl.Resources>
            <my:Person x:Key="personObject" />
        </UserControl.Resources>

        <Grid x:Name="LayoutRoot">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="80" />
                <ColumnDefinition Width="190" />
            </Grid.ColumnDefinitions>

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

            <TextBlock Grid.Column="0" Grid.Row="0" Margin="3" Text="First Name:"
                       VerticalAlignment="Center" />
            <TextBox Grid.Column="1" Grid.Row="0" Margin="3" Name="firstNameTextBox" />
            <TextBlock Grid.Column="0" Grid.Row="1" Margin="3" Text="Last Name:"
                       VerticalAlignment="Center" />
            <TextBox Grid.Column="1" Grid.Row="1" Margin="3" Name="lastNameTextBox" />
            <Button Grid.Column="1" Grid.Row="2" Margin="3" Name="OKButton" Content="OK"
                    Width="100" HorizontalAlignment="Right" />
        </Grid>
    </UserControl>
  3. Find the opening tag of the Grid control (named LayoutRoot), and assign the personObject resource to its DataContext property:
    <Grid x:Name="LayoutRoot" DataContext="{StaticResource personObject}">
  4. The final step is to bind the controls to this data by writing a data binding expression, using the Binding markup expression. Here, we want to associate the FirstName property of the person object with the Text property of the TextBox to show the person's first name; we're using the Path property of the Binding markup expression and the LastName property of the person object with the Text property of the TextBox to show the person's last name:
    <TextBox Grid.Column="1" Grid.Row="0" Margin="3"
             Name="firstNameTextBox" Text="{Binding FirstName, Mode=TwoWay}" />

    <TextBox Grid.Column="1" Grid.Row="1" Margin="3"
             Name="lastNameTextBox" Text="{Binding LastName, Mode=TwoWay}" />

images Note The default property of the Binding markup expression is the Path property, enabling you to specify a value for the Path property without needing to explicitly specify which property you are assigning it to in the binding expression. This means that

Text="{Binding Path=FirstName, Mode=TwoWay}"

can instead be written as

Text="{Binding FirstName, Mode=TwoWay}"

You may have noted that we are explicitly setting the Mode property on the binding expression. The binding mode specifies the direction of the data flow. There are three different binding modes:

  • OneTime: This option enables the target to obtain a value from the source object, after which the binding is discarded, until a new object is assigned to the data context. Changes to the source object will not be propagated back to the target property, and changes to the target property's value in the user interface will not be propagated back to the source object. Use this option when the source object is guaranteed not to change.
  • OneWay: This option enables the target to obtain a value from the source object and be notified when that source value is updated so that the target can update itself with the new value. Changes to the source object will be propagated back to the target property, but changes to the target property's value in the user interface will not be propagated back to the source object. Use this option when the source object may change, but the value cannot be changed in the user interface, such as when binding to a TextBlock control.
  • TwoWay: This option enables the target to obtain a value from the source object, be notified when that source value is updated so that it can update itself with the new value, and update that value with changes made to the target. Changes to the source object will be propagated back to the target property, and changes to the target property's value in the user interface will be propagated back to the source object. Use this option when any changes made to the target in the user interface, such as the Text property of the TextBox control, should update the source object accordingly.

By default, data binding expressions use the OneWay mode. Since changes made to the data in the user interface are not saved back to the source object with OneWay mode, it is therefore particularly necessary for data input controls to explicitly specify TwoWay mode on their binding expressions.

images Note In order for changes made to a data-bound property in the code-behind to be propagated to the user interface, you will need to implement the INotifyPropertyChanged interface on your class and call the PropertyChanged event in the property's setter. Changes to the values of automatically implemented properties will not notify the user interface that they have been updated because, unfortunately, they do not support the INotifyPropertyChanged interface, and hence any changes to them will not be reflected on the screen. However, dependency properties do automatically notify any controls bound to them when their value changes. Further discussion of this topic and examples of implementing the INotifyPropertyChanged interface can be found in Chapter 7.

Any changes made in the text boxes in the user interface will update the properties on the source object that they're bound to automatically. You can prove this by updating the values in the text boxes and then displaying the corresponding property values from the bound object in a message box:

private void OKButton_Click(object sender, RoutedEventArgs e)
{
    Person person = LayoutRoot.DataContext as Person;
    MessageBox.Show("Hello " + person.FullName);
}

images Note You can use many advanced options when binding, such as converting the data in the source object to something different before assigning it to the target (and vice versa) with a value converter, which is particularly useful for converting the data from one type to another, and specifying how invalid data exceptions are handled. These more advanced data binding concepts are covered in Chapter 11.

Binding to a Collection

You will find that binding a control to a collection is a slightly different scenario than binding to a single object. Controls that expect a collection to bind to (for example, DataGrid, ComboBox, and ListBox) have a different property, often named ItemsSource, that the items to be displayed in that control will be populated from; they don't obtain the collection from the DataContext property.

For example, say you have a class representing a collection of Person objects:

public class People : List<Person>
{
    public People()
    {
        this.Add(new Person() { FirstName = "Homer", LastName = "Simpson" });
        this.Add(new Person() { FirstName = "Marge", LastName = "Simpson" });
        this.Add(new Person() { FirstName = "Bart", LastName = "Simpson" });
        this.Add(new Person() { FirstName = "Lisa", LastName = "Simpson" });
        this.Add(new Person() { FirstName = "Maggie", LastName = "Simpson" });
    }
}

and you instantiate this class as a resource in XAML:

<UserControl.Resources>
    <my:People x:Key="peopleCollection" />
</UserControl.Resources>

You can bind this resource to the ItemsSource property of a ListBox control and set its DisplayMemberPath property to the name of the property on the Person object whose value will be displayed for each item in the list:

<ListBox ItemsSource="{StaticResource peopleCollection}" DisplayMemberPath="FullName" />

The ListBox control takes this collection, and it creates a corresponding ListBoxItem object for each Person object in the People collection, which it adds to its Items collection.  The most interesting part of this is that the DataContext property of each ListBoxItem is automatically assigned its corresponding Person object from the collection. This allows you to identify the source object for a particular ListBoxItem—when it is selected, for example—and enables you to template each item and bind to properties on that object.

images Note Visual Studio 2010 provides a very good data binding expression builder from the Properties window, which can be very helpful when learning how to write data binding expressions. Visual Studio 2010 also has a great way to easily create forms, complete with bindings, from an object. Simply add your object as a new data source to the Data Sources tool window, select the type of controls the object and each of its properties should use, and drag it from the Data Sources tool window onto your design surface. You can then change the generated form as you see fit.

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

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