Chapter 6. Introducing Data Binding

What You’ll Learn in This Hour:

  • The syntax used for binding data

  • The different types and modes of data binding

  • Dependency properties

  • Data Context

In this hour we discuss how to bind data in your WPF application. Data binding is one of the most powerful and exciting features of WPF. Data binding enables you to declare, in a very simple manner, how data will interact with the controls and elements in your UI.

What Is Data Binding?

Data binding is a means of connecting the elements of a user interface with the underlying data that the application is interested in. Data binding is declarative rather than imperative. Declarative means that instead of writing a series of commands to be executed, you describe the relation that the data has to the elements in the UI.

For example, in the Font Viewer, we are interested in the collection of currently installed fonts. We would like to display that collection in a ListBox. If we took an imperative approach to the problem, we would write code that would retrieve the set of fonts and then populate the ListBox with items based on the collection. With data binding, we can simply describe the relationship between the set of fonts and the ListBox declaratively in the markup.

Understanding the Data Binding Syntax

The syntax that WPF uses for data binding can be confusing when you first encounter it. In fact, more than one way exists to declare a data binding in XAML. The most common method of declaring a binding uses a feature of XAML called markup extensions. We introduced markup extensions in Hour 2, “Understanding XAML,” and you may remember that they enable you to concisely describe something that would be awkward or too verbose in plain XAML.

Markup extensions are identified by curly brackets ({}), and a data binding extension always begins with the word Binding.

Let’s begin by examining the data bindings on one of the TextBlock elements in the Font Viewer. Consider the following markup:

<TextBlock Text="{Binding ElementName=SampleText, Path=Text}"
           FontFamily="{Binding ElementName=FontList,
                                Path=SelectedItem}"
           FontSize="10"
           TextWrapping="Wrap"
           Margin="0 0 0 4" />

We have two separate data bindings in this markup. The first binds the Text property of the TextBlock to the Text property of a TextBox named SampleText. Note that the Text attribute’s value is a data binding markup extension. Again, it’s important to emphasize that quotation marks are not allowed inside the markup extension. Markup extensions are not XML like the rest of XAML; rather, they are merely string values that are parsed when an application is compiled.

Our binding references two properties: ElementName and Path. The ElementName property tells the binding to look for an element named SampleText in the XAML and to use that as the source of the data. The Path property tells the binding where to find the specific data that interests us in relation to the data source. In this case, we are interested in the Text property on the source.

Examining the second binding on the TextBlock, we interpret the markup extension, {Binding ElementName=FontList, Path=SelectedItem}, to mean that we will use the SelectedItem property of an element named FontList as the data source.

Binding Two Controls Together

Wiring the properties of two controls together is one of the simplest forms of data binding. In the following steps, we bind the font size of a TextBox to a Slider.

  1. Create a new WPF Application project in Visual Studio.

  2. Open Window1.xaml in the designer.

  3. Replace the Grid with a StackPanel using the following markup:

    <StackPanel>
    </StackPanel>
  4. Inside the StackPanel, create a slider with the following markup:

    <Slider x:Name="mySlider"
            Minimum="8"
            Maximum="64" />

    The Maximum and Minimum attributes define the upper and lower bounds of the Slider. We want to restrict the font size to something meaningful. We also use the x:Name attribute to provide a unique name, mySlider, for the control.

  5. Immediately before the Slider, create a TextBlock using the following markup:

    <TextBlock Text="This is fun!"
               FontSize="{Binding ElementName=mySlider, Path=Value}"
               HorizontalAlignment="Center" />

    We added an attribute to horizontally align the TextBlock in the center of the StackPanel. This causes the text to remain centered when the font size changes.

    We bind the FontSize property of the TextBlock to the Value property of mySlider.

    The complete markup is shown in Listing 6.1.

  6. Run the application.

  7. Move the thumb on the slider and observe how the size of the font changes automatically. You’ll also notice that the slider is automatically repositioned as the font grows larger.

Example 6.1. Binding a Slider to a TextBlock

<StackPanel>
    <TextBlock Text="This is fun!"
               FontSize="{Binding ElementName=mySlider,Path=Value}"
               HorizontalAlignment="Center" />
   <Slider x:Name="mySlider"
           Minimum="8"
           Maximum="64" />
</StackPanel>

Two-Way Data Binding

In the preceding example, the data moves in only one direction, from the Slider to the FontSize property. What do we do when we want a user to input some data using either a TextBox or a Slider, and we’d like one to update the other? If the user moves the thumb on the Slider, we want the TextBox to display the Slider’s new value, and if the user enters a value directly in the TextBox, we want the thumb to be repositioned on the Slider.

WPF makes this scenario very easy to implement. Let’s walk through the steps for setting this up:

  1. Create a new WPF Application project in Visual Studio.

  2. Open Window1.xaml in the designer.

  3. Again, replace the Grid with a StackPanel using the following markup:

    <StackPanel>
    </StackPanel>
  4. Inside the StackPanel, create a TextBlock and a Slider with the following markup:

    <TextBox Text="{Binding ElementName=mySlider,Path=Value,Mode=TwoWay}" />
    <Slider x:Name="mySlider"
            Minimum="0"
            Maximum="100"/>

    This time we’ve made the range from 0 to 100.

  5. Run the application.

  6. Move the thumb back and forth and notice that the text changes immediately when the thumb is moved. You’ll also notice that the number displayed has a lot of decimal places included. This is because Slider.Value is a double.

  7. Now enter a number in TextBox between 0 and 100, and then press the Tab key.

  8. Note that the slider did not update its position until the TextBox lost focus.

  9. Enter some non-numeric text, like your name, into the text field and then press the Tab key.

  10. Nothing happens; the thumb does not move and the text does not change. The data cannot be converted to the appropriate type and it’s ignored.

  11. Now enter a value greater than 100 and press the Tab key.

  12. The thumb snaps all the way to the right, and the text is changed back to 100. The Slider cannot accept a value larger than its Maximum. When you enter a value that is larger, the Slider sets Value to the maximum. Because Slider.Value is changed to the maximum value, TextBox.Text is set to the new value.

The behavior in the last step can be confusing. Here is a breakdown of what is happening:

  1. The value of TextBox.Text is changed by the user.

  2. The data binding updates the value of Slider.Value.

  3. The new value is both valid and greater than Slider.Maximum and so the Slider changes Value to the largest possible value allowed.

  4. The data binding updates the value of TextBox.Text.

Take another look at the binding on TextBox.Text:

{Binding ElementName=mySlider, Path=Value, Mode=TwoWay}

We have introduced a new property, Mode. The value of Mode determines how the data will flow between the data source and the target. The possible values for Mode are listed in Table 6.1.

Table 6.1. Modes for Data Binding

Name

Description

OneWay

Changes to the source will update the target.

TwoWay

Changes to the source will update the target, and changes on the target will update the source.

OneTime

The target is set only when the application starts or when the data context is changed.

OneWayToSource

Changes to the target will update the source.

You are not required to set the Mode for a data binding. However it is a good practice to explicitly state the Mode. The default value for Mode is set by the target of the data binding, and different elements have different defaults. The default data binding mode for a TextBox is TwoWay, whereas the default for TextBlock is OneWay. The default values usually make sense, but it’s always a good idea to make your intention clear.

Watch Out!

Occasionally, you may run into a situation where a data binding does not appear to work. If a data binding cannot convert data to an appropriate type, it will fail silently. We demonstrated an example of this when we bound the TextBox.Text and the Slider.Value. The value of the first is a string, and the later is a double. When the data binding received a string value that it could not convert into a double, it did not complain—it just didn’t work.

I have often encountered bugs in my WPF applications where a data source was unexpectedly null, and the application did not throw an exception. Be sure to keep an eye open for this.

Binding to the Collection of Fonts

In the Font Viewer, we bound a ListBox to the collection of currently installed fonts. The .NET Framework BCL provides this collection as a static property called SystemFontFamilies on the class System.Windows.Media.Fonts. This property returns an instance of type ICollection<FontFamily>. Anything that is enumerable can be bound to the ItemsSource property on the ListBox.

You may have noticed that we did not use the same syntax for binding the font collection to the ListBox as we did for binding the controls to one another. We used the following markup extension:

{x:Static Fonts.SystemFontFamilies}

This markup extension is used for referencing static value members on a class. Value members are fields, properties, constants, and enumeration values.

Technically, using x:Static is not a data binding, and WPF treats it a little bit different from standard data binding, but for a simple scenario such as this you can treat it the same.

By the Way

If you would like to have additional information about a markup extension, you’ll need to look for the extension by its class name. The class names are the identifier plus the word “Extension” (don’t include the namespace alias).

For example, to find out more about x:Static you will need to search “StaticExtension”.

Introducing DataContext

As we’ve mentioned before, all the controls in WPF inherit from FrameworkElement. FrameworkElement has a property called DataContext, and it is incredibly useful when it comes to binding data.

DataContext is the “context” or the “starting point” from which the data binding is taking place. In our earlier examples, we explicitly provide a context for the binding with the ElementName property. The Path property points to the location of source data within the given context.

Additionally, if you set the DataContext for a given FrameworkElement, all the children of that element will have the same DataContext. In other words, a control’s DataContext is inherited from its parent.

Let’s consider a new feature for the Font Viewer that will help us demonstrate how DataContext works. Perhaps we would like to display the number of fonts that are currently installed. We know that Fonts.SystemFontFamilies has a Count that returns the number of items in the collection. We can add a ToolTip to the ListBox that will display the count.

Let’s modify the Font Viewer to add this new feature:

  1. Open the Font Viewer project.

  2. Locate the ListBox in MainWindow.xaml.

  3. Replace the existing ListBox with the following markup:

    <ListBox x:Name="FontList"
             DataContext="{x:Static Fonts.SystemFontFamilies}"
             DockPanel.Dock="Left"
             ItemsSource="{Binding}"
             ToolTip="{Binding Path=Count, Mode=OneTime}"
             Width="160" />

    This places the collection of fonts in the DataContext for the ListBox. The ItemsSource is then bound to the DataContext itself. If you don’t specify a Path, the binding points to the context itself. That’s what we want in this case.

    We also added a binding targeting the ToolTip property using the Count property of the font collection as the source. We told the binder to update just once because we don’t expect the number of fonts to change while our application is running.

  4. Run the application, and hover your mouse over the ListBox. Notice that it displays a number as shown in Figure 6.1. A number by itself could be confusing, so let’s add a little explanatory text to the ToolTip to help out the user.

    A very simple ToolTip displaying the number of fonts installed.

    Figure 6.1. A very simple ToolTip displaying the number of fonts installed.

    We’ll change the markup to the following:

    <ListBox x:Name="FontList"
             DataContext="{x:Static Fonts.SystemFontFamilies}"
             DockPanel.Dock="Left"
             ItemsSource="{Binding}"
             Width="160">
        <ListBox.ToolTip>
            <ToolTip>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Path=Count,
                                              Mode=OneTime}"/>
                    <TextBlock Text=" fonts are installed."/>
                </StackPanel>
            </ToolTip>
        </ListBox.ToolTip>
    </ListBox>

    We added a StackPanel because a ToolTip can have only one child and we need the two bits of text. We use an Orientation of Horizontal because we want the TextBlock elements to display next to each other (rather than being on top of one another). The new ToolTip is shown in Figure 6.2.

    A slightly more advanced ToolTip displaying the number of fonts installed.

    Figure 6.2. A slightly more advanced ToolTip displaying the number of fonts installed.

    The interesting part to note here is that the DataContext set on the ListBox propogates all the way down through the StackPanel to the TextBlock.

  5. Run the application. Listing 6.2 contains the complete markup for this example.

Example 6.2. Adding a ToolTip to the Font Viewer

<Window x:Class="TeachYourselfWPF.FontViewer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Teach Yourself WPF: Font Viewer"
        Height="480"
        Width="640">
    <DockPanel Margin="8">
        <Border DockPanel.Dock="Top"
                CornerRadius="6"
                BorderThickness="1"
                BorderBrush="Gray"
                Background="LightGray"
                Padding="8"
                Margin="0 0 0 8">
            <TextBlock FontSize="14"
                       TextWrapping="Wrap">
            Select a font to view from the list below.
            You can change the text by typing in the region at the bottom.
            </TextBlock>
        </Border>
        <ListBox x:Name="FontList"
                 DataContext="{x:Static Fonts.SystemFontFamilies}"
                 DockPanel.Dock="Left"
                 ItemsSource="{Binding}"
                 Width="160">
        <TextBox x:Name="SampleText"
                 DockPanel.Dock="Bottom"
                 MinLines="4"
                 Margin="8 0"
                 TextWrapping="Wrap"
                 ToolTip="Type here to change the preview text.">
                    The quick brown fox jumps over the lazy dog.
        </TextBox>
        <StackPanel Margin="8 0 8 8">
            <TextBlock Text="{Binding Path=Count}" />
            <TextBlock Text="{Binding ElementName=SampleText,
                                      Path=Text}"
                       FontFamily="{Binding ElementName=FontList,
                                            Path=SelectedItem}"
                       FontSize="10"
                       TextWrapping="Wrap"
                       Margin="0 0 0 4" />
            <TextBlock Text="{Binding ElementName=SampleText,
                                      Path=Text}"
                       FontFamily="{Binding ElementName=FontList,
                                            Path=SelectedItem}"
                       FontSize="16"
                       TextWrapping="Wrap"
                       Margin="0 0 0 4" />
            <TextBlock Text="{Binding ElementName=SampleText,
                                      Path=Text}"
                       FontFamily="{Binding ElementName=FontList,
                                            Path=SelectedItem}"
                       FontSize="24"
                       TextWrapping="Wrap"
                       Margin="0 0 0 4" />
            <TextBlock Text="{Binding ElementName=SampleText,
                                      Path=Text}"
                       FontFamily="{Binding ElementName=FontList,
                                            Path=SelectedItem}"
                       FontSize="32"
                       TextWrapping="Wrap" />
        </StackPanel>
    </DockPanel>
</Window>

What Makes Data Binding Work?

Part of the magic of data binding comes from the WPF property system. The WPF property system allows properties to participate in data binding, styling, animation, and several other exciting features. A property that is registered with the WPF property system is called a dependency property.

A property must be a dependency property to be the target of a data binding. If you are not certain as to whether a property on a control is a dependency property, just check the official documentation. Microsoft makes a note in the description indicating whether the property is a dependency property.

Dependency properties include metadata about the property, such as the default value for the property, the default binding mode, and other miscellaneous items.

Dependency properties also provide automatic change notification. In the example earlier this hour, when we bound TextBox.Text to Slider.Value, the Slider.Value was the data source and a dependency property. Slider.Value automatically notified the data binding target because it was a dependency property.

By the Way

Dependency properties are the foundation for much of WPF. You can get a lot out of WPF without becoming an expert in them. However, if you want to take full advantage of the API, then you will want to dig deeper.

There are many good resources on the Web. Be sure to search for both “DependencyObject” as well as “DependencyProperty” when doing your research.

Unlike the target of a data binding, data sources are not required to be dependency properties. However, if you want to use a binding mode other than OneWay, your properties need to implement some mechanism for change notification.

Other than dependency properties, WPF uses two methods for monitoring change notifications. The first is to raise an event in your property setter that is named after the property plus “Changed.” For example, if your property is FirstName, the setter should raise FirstNameChanged. WPF automatically checks for this event and updates data bindings accordingly. We’ll create a custom dependency property in Hour 11, “Output.”

The second method is to implement INotifyPropertyChanged on the class that owns the property. We’ll discuss that in detail in a later hour.

Demonstrating Automatic Change Notification

We are going to create a small application to demonstrate how WPF automatically wires events based on the name of the event. We will build an application that allows a user to enter a name in a text field and then store that name in a custom class. Whenever the user changes the name, we want to immediately display the name above the text field in a larger font. We will also see how to set the DataContext in the code-behind.

  1. Create a new WPF Application in Visual Studio named AutomaticChangeNotification.

  2. Right-click the project and add a class named Person.

  3. Visual Studio will open the Person class in the editor. Add the following code to the class:

             private string _firstName;
             public string FirstName
             {
                 get { return _firstName;  }
                 set { _firstName = value; }
             }
  4. In Window1.xaml, add some rows to the Grid element so that it looks like this:

           <Grid>
               <Grid.RowDefinitions>
                   <RowDefinition Height="auto" />
                   <RowDefinition Height="auto" />
               </Grid.RowDefinitions>
           </Grid>
  5. Beneath the RowDefinitions, but inside the Grid, add the elements:

           <TextBox Text="{Binding Path=FirstName,Mode=TwoWay}"
                    Margin="4"/>
           <Button Grid.Row="1"
                   Height="40"
                   Margin="4"/>

    We are adding the Button right now because the TextBox does not change its data until it loses focus, so we need another control in the window that we can focus on. The data binding on the TextBox is implicitly referencing the data context of its container. After we set the data context to an instance of Person, the binding will point to the FirstName property.

  6. To set the DataContext for the Window to a new instance of Person, open the code-behind for Window1.xaml and edit the constructor so that it reads as follows:

             public Window1()
             {
                 InitializeComponent();
                 DataContext = new Person();
             }
  7. Right-click the setter for the FirstName property, and select Breakpoint, Insert Breakpoint. Figure 6.3 shows the location of the breakpoint.

    Breakpoint on the setter for FirstName.

    Figure 6.3. Breakpoint on the setter for FirstName.

  8. Run the application and enter some text in the TextBox, and then click the Button. When the TextBox changes, we hit our breakpoint.

  9. Stop debugging, and open Window1.xaml. We’re going to bind the content of the Button to FirstName as well. We’re also going to add a handler for the Click event.

    Replace the existing Button markup with this:

       <Button Grid.Row="1"
               Height="40"
               Margin="4"
               Click="Button_Click"
               Content="{Binding Path=FirstName,Mode=TwoWay}" />
  10. In the code-behind for Window1.xaml, add the event handler that changes the FirstName of our Person instance:

       private void Button_Click(object sender, RoutedEventArgs e)
       {
       ((Person)DataContext).FirstName = "My New Name";
       }
  11. Run the application again and click the button. You’ll notice that we still hit our breakpoint, but nothing is bound to the Button. This is because our Person class does not provide any notification when FirstName is changed.

    Watch Out!

    If you enter some text in the TextBox, the Button updates when the TextBox loses focus. This happens even when we haven’t implemented change notification for this class. WPF can do this because the change originates with the data binding system. Because TextBox is using two-way binding, it is initiating the change, and that’s how WPF knows to update the Button. When we change the Person instance in the event handler, WPF doesn’t know anything about it, and that’s why we need to provide the change notification.

  12. Stop debugging, and open Person.cs in the editor. We’re going to raise an event when FirstName is changed; add the following code to the Person class:

    public event EventHandler FirstNameChanged;
    private void OnFirstNameChanged()
    {
        if (FirstNameChanged != null)
        FirstNameChanged(this, EventArgs.Empty);
    }

    Remember, WPF automatically looks for an event based on the property’s name.

  13. Modify the setter for FirstName to raise the event:

    set
    {
        _firstName = value;
        OnFirstNameChanged();
    }
  14. Run the application again, and click the button. This time you will notice that the content of both the Button and the TextBox changes.

Watch Out!

WPF will not look for these events on classes deriving from DependencyObject or on classes implementing INotifyPropertyChanged.

I once created a property on a user control that I wanted to participate in data binding, and I was confused when WPF did not pick up the custom event I was raising when the property was changed. Then I realized that UserControl inherits from DependencyObject, and I implemented my property as a dependency property instead.

Another Data Binding Syntax

So far we have only used the markup extensions to declare data bindings; however, you can declare data binds without using markup extensions at all. Let’s consider our example from the beginning of the hour:

<TextBlock Text="This is fun!"
           FontSize="{Binding ElementName=mySlider,Path=Value}"
           HorizontalAlignment="Center" />

To declare the same binding without using a markup extension, we would write the following:

<TextBlock Text="This is fun!"
           HorizontalAlignment="Center">
    <TextBlock.FontSize>
        <Binding Path="Value"
                 ElementName="mySlider" />
    </TextBlock.FontSize>
</TextBlock>

Notice that the FontSize attribute in the first example becomes the TextBlock.FontSize element in the second example. The markup extension becomes the Binding element, and the properties become attributes.

Even though it’s easier to understand, this syntax is not used very often. It is much more verbose and adds a lot of noise to the markup. However, you may find it useful in situations where a data binding becomes very complicated.

Summary

In this hour we discussed the basics of using data binding in your WPF application. We demonstrated how you can easily wire controls together using only XAML. We examined how markup extensions are used to declare data bindings, as well as considering the alternative syntax using plain XAML.

We looked at how data context works, and implemented a custom class that was able to participate in data binding.

Q&A

Q.

What is the most common format for declaring a data binding in markup?

A.

Markup extensions are the most common means of declaring a data binding. They are identified by the use of curly brackets ({}).The less common format is XAML.

Q.

When a data binding uses the ElementName property, what sort of object will the data source be?

A.

The ElementName property is used to reference other elements in the XAML. We would then know that the data source would be an element whose name was specified by the value of the ElementName property.

Q.

Is x:Static the same as a data binding?

A.

x:Static is another markup extension that is different from the data binding markup extension. It is used to retrieve data from static value members on classes.

Workshop

Quiz

1.

How would the following data binding be interpreted?

{Binding Path=Count}

2.

Convert the following XAML snippet to use the markup extension for data binding.

     <TextBox>
            <TextBox.Text>
                <Binding Path="FirstName"
                         Mode="TwoWay" />
            </TextBox.Text>
     </TextBox>

Answers

1.

WPF would examine the DataContext of the target of the binding and then attempt to locate the value of Count property on whatever object was found in the DataContext.

2.

The XAML snippet is equivalent to the following:

<TextBox Text="{Binding Path=FirstName,Mode=TwoWay}" />
..................Content has been hidden....................

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