CHAPTER 2

Image

Data Binding

Data binding is often thought of as an advanced topic, but there really is no reason for that. Data binding is

  • Critical to writing XAML applications
  • Not very difficult to learn
  • A very powerful technique

The basic idea behind data binding couldn’t be simpler: you are going to provide values to UIElements based on the values of objects or of other UIElements.

Image Note  UIElement is a base class for most of the objects that have a visual appearance and can process input in Windows 8 applications.

Binding to Objects

Let’s take the first case first: binding an UIElement to an object. The target of the binding must be an UIElement, but the source of the binding can be any POCO (Plain Old CLR Object). In other words, the source can be just about anything.

Image Note  When we talk about the target of binding, we mean the control that has a value bound to it. When we talk about the source of binding, we mean the object that has the value from which we will bind.

For example, if you have an object that represents a person, and that Person object has two public properties, Name and Age, you may want to bind those properties to TextBlocks so that you can display them easily without hard-coding the values. In that case, the Person object (POCO) is the source and the TextBlocks (UIElements) are the targets of the binding.

To create this binding, you set the appropriate property (in this case Text) of the UIElement using the syntax of {binding <property>} where <property> is Name, Age, or whatever the public property is. The following code provides a very simple illustration. You begin by declaring an object to which you can bind; this is a POCO object.

class Employee
{
    public string Name { get; set; }
    public string Title { get; set; }

    public Employee(string name, string title)
    {
        Name = name;
        Title = title;
    }

    public static Employee GetEmployee()
    {
        var emp = new Employee( "Tom", "Developer" );
        return emp;
    }

}

As you can see, the Employee class has two public properties. You bind to these properties in the XAML.

<StackPanel Name="xDisplay">
    <StackPanel
        Orientation="Horizontal">
        <TextBlock
            Text="Name:" />
        <TextBlock
            Margin="5,0,0,0"
            Text="{Binding Name}" />
    </StackPanel>
    <StackPanel
        Orientation="Horizontal">
        <TextBlock
            Text="Title:" />
        <TextBlock
            Margin="5,0,0,0"
            Text="{Binding Title}" />
    </StackPanel>
</StackPanel>

Image Note  For this and all XAML examples, please place the XAML shown within the default Grid on the MainPage unless the example indicates otherwise.

The code indicates that you want to bind to the Name and Title properties, respectively, but of which object? It may be that there are many Employee objects around at any given moment. The answer is found in the DataContext. This can be set in the XAML or in the code; here you set it in code, in MainPage.xaml.cs:

protected override void OnNavigatedTo(NavigationEventArgs e)
 {
     xDisplay.DataContext = Employee.GetEmployee();
 }

The DataContext says “I promised you a value in various properties. Please obtain those properties from this object.”

Every UIElement has a DataContext property. You can assign each individually, or you can, as I’ve done here, assign a DataContext further up the visibility tree (in this case, on the StackPanel). The DataContext will be “inherited” by all the UIElements further down the tree unless they have their own DataContext, in which case it will override the ancestor’s DataContext.

Three Data Binding Modes

When data binding, you can designate one of three data binding modes:

  • OneTime
  • OneWay
  • TwoWay

These determine whether and when the control is updated based on changes to the underlying data and vice versa. With OneTime binding, the control is not updated even if the underlying data changes. It is rare to use OneTime binding, but it can be useful in taking a snapshot of the state of a database at any given moment. With OneWay binding, the UI is updated when the underlying data changes, but updating the UI has no effect on the underlying data. With TwoWay binding, changes made to the underlying data are reflected in the UI and changes made by the user in the UI are reflected in the underlying data (and presumably persisted, for example to a database).

If you want TwoWay binding on the TextBlock for Name, you could use the following code:

Text="{Binding Name, Mode=TwoWay}” />

Image Note  The default is OneWay binding.

Typically, TextBlocks are bound with OneWay binding as they are read-only, and TextBoxes (which are read/write) are bound with TwoWay binding.

Binding and INotifyPropertyChanged

Earlier I said that with OneWay binding (and TwoWay, for that matter), the UI is updated when the underlying data changes. This is true, but you have to help it along. You do so by having the class that you are binding to implement INotifyPropertyChanged. This interface has only one event, PropertyChanged. You raise this event each time a property is set in your class, and as a result, the UI is notified and updated.

The classic implementation tests to see if anyone has registered with the event, and if so raises the event using the object itself as the sender and creates a new NotifyPropertyEventArgs object with the name of the property as the argument to the constructor.

Typically, this is all factored out to a helper method called RaisePropertyChanged in the following code. The UI is dead simple—just TextBlocks to hold the prompts for Name and Title, and more TextBlocks to display the (bound) values. The button at the bottom of the StackPanel has an event handler, which will change the value of Name (simulating a change coming from a server). Because of INotifyChanged, when the value of Name changes, it will be immediately reflected in the UI.

<StackPanel Name="LayoutRoot">
     <StackPanel
         Orientation="Horizontal">
         <TextBlock
             Text="Name" />
         <TextBlock
             Margin="5,0,0,0"
             Height="50"
             Width="200"
             Text="{Binding Name}" />
     </StackPanel>
     <StackPanel
         Orientation="Horizontal"
         Margin="0,5,0,0">
         <TextBlock
             Text="Title" />
         <TextBlock
             Margin="5,0,0,0"
             Height="50"
             Width="200"
             Text="{Binding Title}" />
     </StackPanel>
     <Button
         Name="xChange"
         Content="Change"
         Margin="0,5,0,0"
         Click="xChange_Click_1" />
 </StackPanel>

The Employee class implements INotifyPropertyChanged.

Image Note  As you can see, some of the terms in the class will have red squiggly lines under them. Place the cursor on that term, and type control-dot. Visual Studio will offer to add the missing namespace for you. Presto! Your code works.

class Employee  : INotifyPropertyChanged
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            _name = value;
            RaisePropertyChanged();
        }
    }

    private string _title;
    public string Title
    {
        get { return _title; }
        set
        {
            _title = value;

            RaisePropertyChanged();
        }
    }

    private void RaisePropertyChanged(
        [CallerMemberName] string caller = "" )
    {
        if ( PropertyChanged != null )
        {
            PropertyChanged( this, new PropertyChangedEventArgs( caller ) );
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Finally, the codebehind for MainPage.xaml has the event handler for the button.

public sealed partial class MainPage : Page
 {

     Employee emp = new Employee() { Name = "George", Title = "President" };
     public MainPage()
     {
         this.InitializeComponent();
     }

     protected override void OnNavigatedTo(NavigationEventArgs e)
     {
         LayoutRoot.DataContext = emp;
     }

     private void xChange_Click_1( object sender, RoutedEventArgs e )
     {
         emp.Name = "John";
     }
 }

When the Employee is created, the name is set to George and the title to President, which is reflected in the UI. When the button is pushed, it simulates a change to the underlying data by changing the name to John. The UI is updated because the Employee class implements INotifyPropertyChanged.

Image Note  In the calls to RaisePropertyChanged, the name of the property being changed is not passed in. Yet the method is able to create the PropertyChangedEventArgs with the calling method’s name. This is due to the attribute [CallerMemberName], which sets the caller argument to the name of the calling method. Most of the time this is just what you want, but if you need to override the value, you can pass in a text string that will be used instead.

Binding to Other Elements

Earlier I said that the source for data binding can be any CLR object. This includes UIElements themselves. It is possible (and common!) to bind one UIElement to a value in another. For example, you might bind the IsActive property of a ProgressRing to the IsChecked property of a checkbox, as shown in the next example.

Image Note  A ProgressRing is used to show that work is being done when it is not known how long that work will take.

<StackPanel>
     <StackPanel
         Orientation="Horizontal"
         HorizontalAlignment="Left"
         >
         <TextBlock
             Text="ProgressRing:"
             VerticalAlignment="Center"
             Margin="0,0,20,0" />
         <Border
             BorderThickness="1"
             BorderBrush="#44000000"
             Padding="10">
             <ProgressRing
                 x:Name="ProgressRing1"
                 IsActive="{Binding IsChecked, ElementName=ActiveCB}" />
         </Border>
     </StackPanel>
     <CheckBox
         Name="ActiveCB"
         Content="Active?" />
 </StackPanel>

Notice that this example has no codebehind. The ProgressRing is active or not depending on the value of its IsActive property. That property is bound to the IsChecked property of the CheckBox. When you check the CheckBox, the ProgressRing becomes active.

Binding and Data Conversion

At times the data in your business object (the source for your binding) and the target UIElement may not have an exact type match. For example, if your Employee class wants to keep track of the start date for each employee, a sensible way to do so is with a DateTime object. However, when you display that data, you want to use a Text object, and you may not want the entire default conversion of a DateTime to a string. To rectify this problem, you can create a class that performs a conversion from one type to another (in this case, from DateTime to string). This class will implement IValueConverter and will have two methods: Convert and ConvertBack. In the following code, you modify the Employee class to add the startDate:

private DateTime _startDate;
public DateTime StartDate
{
    get { return _startDate; }
    set { _startDate = value; RaisePropertyChanged(); } }

You then add a new converter class.

public class DateConverter : IValueConverter
 {
     public object Convert( object value, Type targetType, object parameter, string language )
     {
         DateTime date = (DateTime)value;
         return date.ToString("d");
     }

     public object ConvertBack( object value, Type targetType, object parameter, string language
)
     {
         string strValue = value as string;
         DateTime resultDateTime;
         if ( DateTime.TryParse( strValue, out resultDateTime ) )
         {
             return resultDateTime;
         }
         throw new Exception( "Unable to convert string to date time" );
     }
 }

Image Note  Notice the new use of formatting in date.ToString. See http://msdn.microsoft.com/en-us/library/zdtaw1bw.aspx.

You can now create a resource for this class in the XAML. Resources are reusable chunks of code. In this case, since this is a Page.Resource, the code is reusable anywhere in this page. Place the following section below the page declaration but before the declaration of the grid:

<Page.Resources>
    <local:DateConverter
        x:Key="DateToStringConverter" />
</Page.Resources>

Then you use that resource when you bind to the StartDate.

<StackPanel
    Orientation="Horizontal"
    Margin="0,5,0,0">
    <TextBlock
        Text="Start Date" />
    <TextBlock
        Margin="5,0,0,0"
        Height="50"
        Width="200"
        Text="{Binding StartDate, Converter={StaticResource DateToStringConverter } }" />
</StackPanel>

As a result, the StartDate property is a DateTime object in the Employee object, but it is represented as a short string in the UI.

Binding to Lists

Often, rather than binding to a single object, you will want to bind to a collection of objects. There are a number of controls for handling collections, and you will examine them in coming chapters. For now, let’s focus on how the binding will work.

The trick is to teach the control how to display the bound data. You do this most often with a DataTemplate, a template or set of XAML that will be reproduced for each member of the collection. In the following code, you create a slightly modified Employee class. Each Employee has two properties, Name and Title. You also give the class a static method that returns a list of employees, simulating retrieving data from a web service or other data source.

class Employee : INotifyPropertyChanged
    {
        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                RaisePropertyChanged();
            }
        }

        private string _title;
        public string Title
        {
            get { return _title; }
            set
            {
                _title = value;
                RaisePropertyChanged();
            }
        }

        private void RaisePropertyChanged(
            [CallerMemberName] string caller = "" )
        {
            if ( PropertyChanged != null )
            {
                PropertyChanged( this, new PropertyChangedEventArgs( caller ) );
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public static ObservableCollection<Employee> GetEmployees()
        {
            var employees = new ObservableCollection<Employee>();
            employees.Add( new Employee() { Name = "Washington", Title = "President 1" } );
            employees.Add( new Employee() { Name = "Adams", Title = "President 2" } );
            employees.Add( new Employee() { Name = "Jefferson", Title = "President 3" } );
            employees.Add( new Employee() { Name = "Madison", Title = "President 4" } );
            employees.Add( new Employee() { Name = "Monroe", Title = "President 5" } );
            return employees;
        }

    }

Notice that the collection type you use for Employee is an ObservableCollection. This type implements INotifyPropertyChanged and INotifyCollectionChanged, and so will inform the UI that the collection has changed. Note that to ensure notification if an individual element in the collection is changed (e.g., an employee’s name changes), you must also implement INotifyPropertyChanged on the element type itself.

All you need now is to bind this collection of employees to a control that handles collections of data, such as a ComboBox. The ComboBox has no way of knowing, however, how to display an Employee. Left to its own devices, it will just display whatever ToString resolves to. You could override ToString, but it is much more efficient and flexible to teach the ComboBox how to display the Employee exactly as you want, and you do that with a DataTemplate, as shown in the following code:

<ComboBox
    x:Name="ComboBox1"
    ItemsSource="{Binding}"
    Foreground="Black"
    FontSize="30"
    Height="50"
    Width="550">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <StackPanel
                Orientation="Horizontal"
                Margin="2">
                <TextBlock
                    Text="Name:"
                    Margin="2" />
                <TextBlock
                    Text="{Binding Name}"
                    Margin="2" />
                <TextBlock
                    Text="Title:"
                    Margin="10,2,0,2" />
                <TextBlock
                    Text="{Binding Title}"
                    Margin="2" />
            </StackPanel>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

The ItemTemplate controls how each item is displayed and the DataTemplate lays out how the data is displayed. In this case, you are displaying the data by laying out four TextBlocks horizontally so that each Employee appears on a single line in the ComboBox. Notice that you bind to the ItemsSource property with the keyword binding but you don’t specify what you’re binding to. This is done by setting the DataContext in the codebehind.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    ComboBox1.ItemsSource = Employee.GetEmployees();
}

You set this in the codebehind because you are mimicking the action of retrieving the resource (Employees) from a database or other service. The result is that when the ComboBox is opened, all the Employee objects are displayed, each according to the DataTemplate, as seen in Figure 2-1.

Image

Figure 2-1. ComboBox with DataTemplate and binding

Image Note  President Kennedy was entertaining a roomful of Nobel Prize winners when he said, “I think this is the most extraordinary collection of talent, of human knowledge, that has ever been gathered at the White House—with the possible exception of when Thomas Jefferson dined alone.”

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

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