Data Binding

Once we've got a set of controls and a way to lay them out, we still need to fill them with data and keep that data in sync with wherever the data actually lives. (Controls are a great way to show data but a poor place to keep it.) For example, imagine that we'd like to build a WPF application for keeping track of people's nicknames. Something like Figure 1-15 would do the trick.

Data binding to a collection of custom types

Figure 1-15. Data binding to a collection of custom types

In Figure 1-15, we've got two TextBox controls, one for the name and one for the nickname. We've also got the actual nickname entries in a ListBox in the middle and a Button to add new entries. We could easily build the core data of such an application with a class, as shown in Example 1-25.

Example 1-25. A custom type with data binding support

public class Nickname : INotifyPropertyChanged {
    // INotifyPropertyChanged Member
    public event PropertyChangedEventHandler PropertyChanged;
    void Notify(string propName) {
      if( PropertyChanged != null ) {
        PropertyChanged(this, new PropertyChangedEventArgs(propName));
    }
  }

  string name;
  public string Name {
    get { return name; }
    set {
      name = value;
      Notify("Name"); // notify consumers
    }
  }

  string nick;
  public string Nick {
    get { return nick; }
    set {
      nick = value;
      Notify("Nick"); // notify consumers
    }
  }

  public Nickname(  ) : this("name", "nick") { }
  public Nickname(string name, string nick) {
    this.name = name;
    this.nick = nick;
  }
}

This class knows nothing about data binding, but it does have two public properties that expose the data, and it implements the standard INotifyPropertyChanged interface to let consumers of this data know when it has changed.

In the same way that we have a standard interface for notifying consumers of objects when they change, we also have a standard way to notify consumers of collections of changes, called INotifyCollectionChanged. WPF provides an implementation of this interface, called ObservableCollection, which we'll use so that appropriate events are fired when Nickname objects are added or removed (Example 1-26).

Example 1-26. A custom collection type with data binding support

  // Notify consumers
  public class Nicknames : ObservableCollection<Nickname> { }

Around these classes, we could build nickname management logic that looks like Example 1-27.

Example 1-27. Making ready for data binding

// Window1.xaml.cs
...
namespace DataBindingDemo {
  public class Nickname : INotifyPropertyChanged {...}
  public class Nicknames : ObservableCollection<Nickname> { }

  public partial class Window1 : Window {
    Nicknames names;

    public Window1(  ) {
      InitializeComponent(  );
      this.addButton.Click += addButton_Click;

      // create a nickname collection
      this.names = new Nicknames(  );

      // make data available for binding
      dockPanel.DataContext = this.names;
    }

    void addButton_Click(object sender, RoutedEventArgs e) {
      this.names.Add(new Nickname(  ));
    }
  }
}

Notice that the window's class constructor adds a click event handler to add a new nickname and creates the initial collection of nicknames. However, the most useful thing that the Window1 constructor does is set its DataContext property so as to make the nickname data available for data binding.

In WPF, data binding is about keeping object properties and collections of objects synchronized with one or more controls' views of the data. The goal of data binding is to save you the time required to write the code to update the controls when the data in the objects changes, and to update the data when the user edits the data in the controls. The synchronization of the data to the controls depends on the INotifyPropertyChanged and INotifyCollectionChanged interfaces that we've been careful to use in our data and data collection implementations.

For example, because the collection of our example nickname data and the nickname data itself both notify consumers when there are changes, we can hook up controls using WPF data binding, as shown in Example 1-28.

Example 1-28. An example data binding usage

<!-- Window1.xaml -->
<Window ...>
  <DockPanel x:Name="dockPanel">
    <TextBlock DockPanel.Dock="Top">
      <TextBlock VerticalAlignment="Center">Name: </TextBlock>
      <TextBox Text="{Binding Path=Name}" />
      <TextBlock VerticalAlignment="Center">Nick: </TextBlock>
      <TextBox Text="{Binding Path=Nick}" />
    </TextBlock>
    <Button DockPanel.Dock="Bottom" x:Name="addButton">Add</Button>
    <ListBox
        ItemsSource="{Binding}"
        IsSynchronizedWithCurrentItem="True" />
   </DockPanel>
</Window>

This XAML lays out the controls as shown in Figure 1-15 using a dock panel to arrange things top to bottom and a text block to contain the editing controls. The secret sauce that takes advantage of data binding is the {Binding} values in the control attributes instead of hardcoded values. By setting the Text property of the TextBox to {Binding Path=Name}, we're telling the TextBox to use data binding to peek at the Name property out of the current Nickname object. Further, if the data changes in the Name TextBox, the Path is used to poke the new value back in.

The current Nickname object is determined by the ListBox because of the IsSynchronizedWithCurrentItem property, which keeps the TextBox controls showing the same Nickname object as the one that's currently selected in the ListBox. The ListBox is bound to its data by setting the ItemsSource attribute to {Binding} without a Path statement. In the ListBox, we're not interested in showing a single property on a single object, but rather all of the objects at once.

But how do we know that both the ListBox and the TextBox controls are sharing the same data? That's where setting the dock panel's DataContext comes in (back in Example 1-27). In the absence of other instructions, when a control's property is set using data binding, it looks at its own DataContext property for data. If it doesn't find any, it looks at its parent and then its parent, and so on, all the way up the tree. Because the ListBox and the TextBox controls have a common parent that has a DataContext property set (the DockPanel), all of the data bound controls will share the same data.

XAML Markup Extensions

Before we take a look at the results of our data binding, let's take a moment to discuss XAML markup extensions, which is what you're using when you set an attribute to something inside of curly braces (e.g., Text="{Binding Path=Name}"). Markup extensions add special processing to XAML attribute values. For example, this:

<TextBox Text="{Binding Path=Name}" />

is just a shortcut for this (which you'll recognize as the property element syntax):

<TextBox.Text>
  <Binding Path="Name" />
</TextBox.Text>

For a complete discussion of markup extensions, as well as the rest of the XAML syntax, read Appendix A.

Data Templates

With the data binding markup syntax explained, let's turn back to our example data binding application, which so far doesn't look quite like what we had in mind, as seen in Figure 1-16.

ListBox showing objects of a custom type without special instructions

Figure 1-16. ListBox showing objects of a custom type without special instructions

It's clear that the data is making its way into the application, because the currently selected name and nickname are shown for editing. The problem is that, unlike the TextBox controls, which were each given a specific field of the Nickname object to show, the ListBox is expected to show the whole thing. Lacking special instructions, it's calling the ToString method of each object, which results in only the name of the type. To show the data, we need to compose a data template, like the one in Example 1-29.

Example 1-29. Using a data template

<ListBox
    ItemsSource="{Binding}"
    IsSynchronizedWithCurrentItem="True">

    <ListBox.ItemTemplate>
    <DataTemplate>
      <TextBlock>
        <TextBlock Text="{Binding Path=Name}" />:
        <TextBlock Text="{Binding Path=Nick}" />
      </TextBlock>
    </DataTemplate>
  </ListBox.ItemTemplate>

</ListBox>

A data template is a set of elements that should be inserted somewhere. In our case, we are specifying a data template to be inserted for each listbox item by setting the ItemTemplate property. In Example 1-29, we've composed a data template from a text block that flows together two other text blocks, each bound to a property on a Nickname object separated by a colon, as shown back in Figure 1-15.

At this point, we've got a completely data-bound application. As data in the collection or the individual objects changes, the UI will be updated, and vice versa. However, there is a great deal more to say on this topic, including binding to XML and relational data, master-detail binding, and hierarchical binding, which you'll see in Chapter 6 and Chapter 7.

Get Programming WPF, 2nd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.