Chapter 6. Simple Data Binding

The purpose of most applications is to display data to users and, often, to let them edit that data. Your job as the application developer is to bring the data in from a variety of sources that expose their data in object, hierarchical, or relational format. Regardless of where the data comes from or the format it’s in, there are several things that you’ll most likely need to do with the data, including showing it, converting it, sorting it, filtering it, grouping it, relating one part of it to another part, and, more often than not, editing it. Without some kind of engine for shuttling data back and forth between data sources and controls, you’re going to be writing a great deal of code. With WPF’s data binding engine, you get more features with less code, which is always a nice place to be.

Without Data Binding

Consider a very simple application for editing a single person’s name and age, as shown in Figure 6-1.

An exceedingly simple application
Figure 6-1. An exceedingly simple application

Figure 6-1 can be implemented with some simple XAML, as shown in Example 6-1.

Example 6-1. A simple Person editor layout
<!-- Window1.xaml -->
<Window ...>
  <Grid>

    ...
    <TextBlock ...>Name:</TextBlock>
    <TextBox Name="nameTextBox" ... />
    <TextBlock ...>Age:</TextBlock>
    <TextBox Name="ageTextBox" ... />
    <Button Name="birthdayButton" ...>Birthday</Button>
    </Grid>
    </Window>

We can represent the data to be shown in our simple application in a simple class (see Example 6-2).

Example 6-2. A simple Person class
public class Person {
  string name;
  public string Name {
    get { return this.name; }
    set { this.name = value; }
  }

  int age;
  public int Age {
    get { return this.age; }
    set { this.age = value; }
  }

  public Person(  ) {}
  public Person(string name, int age) {
    this.name = name;
    this.age = age;
  }
}

With the Person class, Example 6-3 shows a naïve implementation of the UI of our application.

Example 6-3. Naïve Person editor code
// Window1.xaml.cs
...
public class Person {...}

public partial class Window1 : Window {
  Person person = new Person("Tom", 11);

  public Window1(  ) {
    InitializeComponent(  );

     // Fill initial person fields
    this.nameTextBox.Text = person.Name;
    this.ageTextBox.Text = person.Age.ToString(  );

    // Handle the birthday button click event
    this.birthdayButton.Click += birthdayButton_Click;
  }

  void birthdayButton_Click(object sender, RoutedEventArgs e) {
    ++person.Age;
    MessageBox.Show(
      string.Format(
        "Happy Birthday, {0}, age {1}!",
       person.Name,
       person.Age),
     "Birthday");
  }
}

The code in Example 6-3 creates a Person object and initializes the text boxes with the Person object properties. When the Birthday button is pressed, the Person object’s Age property is incremented and the updated Person data is shown in a message box, as shown in Figure 6-2.

Our simple application is too simple
Figure 6-2. Our simple application is too simple

Our simple application implementation is, in fact, too simple. The change in the Person Age property does show up in the message box, but it does not show up in the main window. One way to keep the application’s UI up-to-date is to write code that, whenever a Person object is updated, manually updates the UI at the same time:

void birthdayButton_Click(object sender, RoutedEventArgs e) {
  ++person.Age;
  // Manually update the UI
  this.ageTextBox.Text = person.Age.ToString(  );

  MessageBox.Show(
    string.Format(
      "Happy Birthday, {0}, age {1}!",
      person.Name,
      person.Age),
    "Birthday");
}

With a single line of code, we’ve “fixed” our application. This is a seductive and popular road, but it does not scale as the application gets more complicated and requires more of these “single” lines of code. To get beyond the simplest of applications, we’ll need something better.

Object Changes

A more robust way for the UI to track object changes is for the object to raise an event when a property changes. The right way for an object to do this is with an implementation of the INotifyPropertyChanged interface, as shown in Example 6-4.

Example 6-4. A class that supports property change notification
using System.ComponentModel; // INotifyPropertyChanged
...
public class Person : INotifyPropertyChanged{
  // INotifyPropertyChanged Members
  public event PropertyChangedEventHandler PropertyChanged;

  protected void Notify(string propName) {
    if( this.PropertyChanged != null ) {
      PropertyChanged(this, new PropertyChangedEventArgs(propName));
    }
  }

  string name;
  public string Name {
    get { return this.name; }
    set {
      if( this.name == value ) { return; }
      this.name = value;
      Notify("Name");
    }
  }

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

  public Person(  ) {}
  public Person(string name, int age) {
    this.name = name;
    this.age = age;
  }
}

In Example 6-4, when either of the Person properties changes (due to the implementation of the Birthday button Click handler), a Person object raises the PropertyChanged event. We could use this event to keep the UI synchronized with the Person properties, as shown in Example 6-5.

Example 6-5. Simple Person editor code
// Window1.xaml.cs
...
public class Person : INotifyPropertyChanged {...}

public partial class Window1 : Window {
  Person person = new Person("Tom", 11);

  public Window1(  ) {
    InitializeComponent(  );

    // Fill initial person fields
    this.nameTextBox.Text = person.Name;
    this.ageTextBox.Text = person.Age.ToString(  );

    // Watch for changes in Tom's properties
    person.PropertyChanged += person_PropertyChanged;

    // Handle the birthday button click event
    this.birthdayButton.Click += birthdayButton_Click;
  }

  void person_PropertyChanged(
     object sender,
     PropertyChangedEventArgs e) {

     switch( e.PropertyName ) {
       case "Name":
       this.nameTextBox.Text = person.Name;
       break;

       case "Age":
       this.ageTextBox.Text = person.Age.ToString(  );
       break;
    }
  }

  void birthdayButton_Click(object sender, RoutedEventArgs e) {
    ++person.Age; // person_PropertyChanged will update ageTextBox
    MessageBox.Show(
      string.Format(
        "Happy Birthday, {0}, age {1}!",
       person.Name,
       person.Age),
     "Birthday");
  }
}

Example 6-5 shows an example of a single instance of the Person class that’s created when the main window first comes into existence, initializing the name and age text boxes with the initial person values and then subscribing to the property change event to keep the text boxes up-to-date as the Person object changes. With this code in place, the Birthday button Click event handler doesn’t have to manually update the text boxes when it updates Tom’s age; instead, updating the Age property causes a cascade of events that keeps the age text box up-to-date with the Person object’s changes, as shown in Figure 6-3.

Keeping the UI up-to-date with changes in the object
Figure 6-3. Keeping the UI up-to-date with changes in the object

The steps are as follows:

  1. User clicks on button, which causes Click event to be raised.

  2. Click handler gets the age (11) from the Person object.

  3. Click handler sets the age (12) on the Person object.

  4. Person Age property setter raises the PropertyChanged event.

  5. PropertyChanged event is routed to event handler in the UI code.

  6. UI code updates the age TextBox from “11” to “12.”

  7. Button click event handler displays a message box showing the new age (“12”).

By the time the message box is shown with Tom’s new age, the age text box in the window has already been updated, as shown in Figure 6-4.

Manually populating two WPF controls with two object properties
Figure 6-4. Manually populating two WPF controls with two object properties

By handling the PropertyChanged event, we ensure that when the data changes, the UI is updated to reflect that change. However, that solves only half the problem; we still need to handle changes in the UI and reflect them back to the object.

UI Changes

Without some way to track changes from the UI back into the object, we could easily end up with a case where the user has made some change (like changing the person’s name), shows the object’s data (as happens when clicking the Birthday button), and expects the change to have been made, only to be disappointed with Figure 6-5.

The need to keep UI and data in sync
Figure 6-5. The need to keep UI and data in sync

Notice in Figure 6-5 that the Name is “Thomsen Frederick” in the window, but “Tom” in the message box, which shows that although the UI has been updated, the underlying object has not. To fix this problem, we need only watch for the Text property in our TextBox object to change, updating the Person object as appropriate (see Example 6-6).

Example 6-6. Tracking changes in the UI
public partial class Window1 : Window {
  Person person = new Person("Tom", 11);

  public Window1(  ) {
    InitializeComponent(  );

    // Fill initial person fields
    this.nameTextBox.Text = person.Name;
    this.ageTextBox.Text = person.Age.ToString(  );

    // Watch for changes in Tom's properties
    person.PropertyChanged += person_PropertyChanged;

    // Watch for changes in the controls
    this.nameTextBox.TextChanged += nameTextBox_TextChanged;
    this.ageTextBox.TextChanged += ageTextBox_TextChanged;

    // Handle the birthday button click event
    this.birthdayButton.Click += birthdayButton_Click;
  }

  ...

  void nameTextBox_TextChanged(object sender, TextChangedEventArgs e) {
    person.Name = nameTextBox.Text;
  }

  void ageTextBox_TextChanged(object sender, TextChangedEventArgs e) {
    int age = 0;
    if( int.TryParse(ageTextBox.Text, out age) ) {
      person.Age = age;
    }
  }
  void birthdayButton_Click(object sender, RoutedEventArgs e) {
    ++person.Age;

    // nameTextBox_TextChanged and ageTextBox_TextChanged
    // will make sure the Person object is up-to-date
    // when it's displayed in the message box
    MessageBox.Show(
      string.Format(
        "Happy Birthday, {0}, age {1}!",
       person.Name,
       person.Age),
     "Birthday");
  }
}

Figure 6-6 shows the name changes in the UI correctly propagating to the Person object.

Manually keeping properties and controls in sync
Figure 6-6. Manually keeping properties and controls in sync

Now, regardless of where the data changes, both the Person object and the UI showing the Person object are kept synchronized. And although we’ve gotten the functionality we wanted, we had to write quite a bit of code to make it happen:

  • Window1 constructor code to set controls to initial values, converting data to strings as appropriate

  • Window1 constructor code to hook up the PropertyChanged event to track the Person object’s property changes

  • PropertyChanged event handler to grab the updated data from the Person object, converting data to strings as appropriate

  • Window1 constructor code to hook up the TextBox object’s TextChanged event to track the UI changes

  • TextChanged event handlers to push the updated TextBox data into the Person object, converting the data as appropriate

This code allows us to write our Birthday button Event handler safe in the knowledge that all changes are synchronized when we display the message box. However, it’s easy to imagine how this code could quickly get out of hand as the number of object properties or the number of objects we’re managing grows. Plus, this seems like such a common thing to want to do that someone must have already provided a simpler way to do this. And in fact, someone has; it’s called data binding.

Data Binding

Our manual code to keep the data and the UI synchronized has the effect of manually binding together two pairs of properties, each pair composed of one property on the Person object and the Text property on a TextBox object. In WPF, data binding is the act of registering two properties with the data binding engine and letting the engine keep them synchronized, converting types as appropriate, as shown in Figure 6-7.

The synchronization and conversion duties of data binding
Figure 6-7. The synchronization and conversion duties of data binding

Bindings

We can register two properties to be kept in sync by the data binding engine using an instance of a Binding object, as shown in Example 6-7.

Example 6-7. Binding a UI target property to a data source property
<TextBox ...>
  <TextBox.Text>
     <Binding Path="Age" />
  </TextBox.Text>
</TextBox>

In Example 6-7, we’ve used the property element syntax introduced in Chapter 1 to create an instance of the Binding markup extension class and initialize its Path property to Age. This establishes the synchronization relationship with the Text property of the TextBox object. Using the binding markup extension syntax (also introduced in Chapter 1), we can shorten Example 6-7 to the code snippet shown in Example 6-8.

Example 6-8. The shortcut binding syntax
<TextBox Text="{Binding Path=Age}" />

As an even shorter cut, you can drop the Path designation altogether and the Binding will still know what you mean (see Example 6-9).

Example 6-9. The shortest cut binding syntax
<TextBox Text="{Binding Age}" />

I prefer to be more explicit, so I won’t use the syntax in Example 6-9, but I won’t judge if you like it. As an example of something more exotic, Example 6-10 sets more than one attribute of a binding.

Example 6-10. A more full-featured binding example, longhand
<TextBox ...>
  <TextBox.Foreground>
    <Binding Path="Age" Mode="OneWay" Source="{StaticResource Tom}"
                Converter="{StaticResource ageConverter}" />
  </TextBox.Foreground>
</TextBox>

We’ll see what all of these Binding properties mean directly. You might also be interested in how to pack multiple binding attribute settings using the shortcut syntax. To accomplish this, simply comma-delimit the name-value pairs, using spaces and newlines as convenient (see Example 6-11).

Example 6-11. A more full-featured binding example, shorthand
<TextBox ...
  Foreground="{Binding Path=Age, Mode=OneWay, Source={StaticResource Tom},
                    Converter={StaticResource ageConverter}}" />

Table 6-1 shows the list of available properties on a Binding object, many of which you’ll see described in more detail later in this chapter.

Table 6-1. The Binding class’s properties

Property

Meaning

BindsDirectlyToSource

Defaults to False. If set to True, indicates a binding to the parameters of a DataSourceProvider (like the ObjectDataProvider discussed later in this chapter), instead of to the data returned from the provider. See the BindingToMethod sample included with this book for an example.

Converter

An implementation of IValueConverter to use to convert values back and forth from the data source. Discussed later in this chapter.

ConverterCulture

Optional parameter passed to the IValueConverter methods indicating the culture to use during conversion.

ConverterParameter

Optional application-specific parameter passed to the IValueConverter methods during conversion.

ElementName

Used when the source of the data is a UI element as well as the target. Discussed later in this chapter.

FallbackValue

The value to use in case retrieving the value from the data source has failed, one of the parts of a multipart path is null, or the binding is asynchronous and the value hasn’t yet been retrieved.

IsAsync

Defaults to False. When set to True, gets and sets the data on the source asynchronously. Uses the FallbackValue while the data is being retrieved.

Mode

One of the BindingMode values: TwoWay,OneWay, OneTime, OneWayToSource, or Default.

NotifyOnSourceUpdated

Defaults to False. Whether to raise the SourceUpdated event or not.

NotifyOnTargetUpdated

Defaults to False. Whether to raise the TargetUpdated event or not.

NotifyOnValidationError

Defaults to False. Whether to raise the Validation.Error attached event or not. Discussed later in this chapter.

Path

Path to the data of the data source object. Use the XPath property for XML data.

RelativeSource

Used to navigate to the data source relative to the target. Discussed later in this chapter.

Source

A reference to the data source to be used instead of the default data context.

UpdateSourceExceptionFilter

Optional delegate to handle errors raised while updating the data source. Valid only if accompanied by an ErrorValidationRule (discussed later in this chapter).

UpdateSourceTrigger

Determines when the data source is updated from the UI target. Must be one of the UpdateSourceTrigger values: PropertyChanged, LostFocus, Explicit, or Default. Discussed in Chapter 7.

ValidationRules

Zero or more derivations of the ValidationRule class. Discussed later in this chapter.

XPath

XPath to the data on the XML data source object. Use the Path property for non-XML data. Discussed in Chapter 7.

The Binding class has all kinds of interesting facilities for managing the binding between two properties, but the one that you’ll most often set is the Path property.[26] For most cases, you can think of the Path as the name of the property on the object serving as the data source. So, the binding statement in Example 6-8 is creating a binding between the Text property of the TextBox and the Age property of some object to be named later, as shown in Figure 6-8.

Binding targets and sources
Figure 6-8. Binding targets and sources

In this binding, the TextBox control is the binding target, as it acts as a consumer of changes to the binding source, which is the object that provides the data. The binding target can be any WPF element, but you’re only allowed to bind to the element’s dependency properties (described in Chapter 1.

On the other hand, you can bind to any public CLR property or dependency property on the binding source object.[27] The binding source is not named in this example specifically so that we can have some freedom as to where it comes from at runtime and so that it’s easier to bind multiple controls to the same object (like our name and age text box controls bound to the same Person object).

Commonly, the binding source data comes from a data context.

Implicit Data Source

A data context is a place for bindings to look for the data source if they don’t have any other special instructions (which we’ll discuss later). In WPF, every FrameworkElement and every FrameworkContentElement has a DataContext property. The DataContext property is of type Object, so you can plug anything you like into it (e.g., string, Person, List<Person>, etc.). When looking for an object to use as the binding source, the binding object logically traverses up the tree from where it’s defined, looking for a DataContext property that has been set.[28]

This traversal is handy because it means that any two controls with a common logical parent can bind to the same data source. For example, both of our text box controls are children of the grid, and they each search for a data context, as shown in Figure 6-9.

Searching the element tree for a non-null DataContext
Figure 6-9. Searching the element tree for a non-null DataContext

The steps work like this:

  1. The binding looks for a DataContext that has been set on the TextBox itself.

  2. The binding looks for a DataContext that has been set on the Grid.

  3. The binding looks for a DataContext that has been set on the Window.

Providing a DataContext value for both of the text box controls is a matter of setting the shared Person object as a value of the grid’s DataContext property in the Window1 constructor, as shown in Example 6-12.

Example 6-12. Editor code simplified with data binding
// Window1.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;

namespace WithBinding {
  public partial class Window1 : Window {
    Person person = new Person("Tom", 11);

    public Window1(  ) {
    InitializeComponent(  );

    // Let the grid know its data context
    grid.DataContext = person;

    this.birthdayButton.Click += birthdayButton_Click;
  }

  void birthdayButton_Click(object sender, RoutedEventArgs e) {
    // Data binding keeps person and the text boxes synchronized
    ++person.Age;
    MessageBox.Show(
       string.Format(
         "Happy Birthday, {0}, age {1}!",
        person.Name,
        person.Age),
       "Birthday");
    }
  }
}

So, although the functionality of our app is the same as shown in Figure 6-6, the data synchronization code has been reduced to a binding object for each property in the XAML where data is to be shown and a data context for the bindings to find the data. There is no need for the UI initialization code or the event handlers that copy and convert the data (notice that no code has been elided from Example 6-12).

To be clear, the use of the INotifyPropertyChanged implementation is a required part of this example. This is the interface that WPF’s data binding engine uses to keep the UI synchronized when an object’s properties change. Without it, a UI change can still propagate to the object, but the binding engine will have no way of knowing when the object changes outside of the UI.

Tip

It’s not quite true that the binding engine will have no way of knowing when a change happens on an object that does not implement the INotifyPropertyChanged interface. Another way it can know is if the object implements the PropertyName Changed events as proscribed in .NET 1.x data binding (e.g., SizeChanged, TextChanged, etc.), with which WPF maintains backward compatibility. Another way is a manual call to the UpdateTarget method on the BindingExpression object associated with the Binding in question. For example:

BindingOperations.GetBindingExpression(
  ageTextBox, TextBox.TextProperty).UpdateTarget(  );

Without rebinding or setting the data again manually, the call to UpdateTarget is your only option if the data source provides no notifications and you have no access to the source code. However, it’s safe to say that an implementation of INotifyPropertyChanged is the recommended way to enable property change notifications in WPF data binding.

Data Islands

Although our application is attempting to simulate a more complicated application that, perhaps, loads its “person data” from some serialized form and saves it between application sessions, it’s not hard to imagine cases where some data is known at compile time (e.g., sample data like our Tom).

As discussed in Chapter 1, XAML is a language for describing object graphs, so practically any type with a default constructor can be initialized in XAML (the default constructor is needed because XAML has no syntax for calling a nondefault constructor).[29] Luckily, as you’ll recall from Example 6-4, our Person class has a default constructor, so we can create an instance of it in our application’s XAML, as shown in Example 6-13.

Example 6-13. Creating an instance of a custom type in XAML
<Window ... xmlns:local="clr-namespace:WithBinding">
  <Window.Resources>
    <local:Person x:Key="Tom" Name="Tom" Age="11" />
  </Window.Resources>
  <Grid>...</Grid>
</Window

Here we’ve created an “island” of data (sometimes called a data island) inside the window’s Resources element, bringing the Person type in using the clr-namespace syntax described in Chapter 1.

With a named Person in our XAML code, we can declaratively set the grid’s DataContext instead of setting it in the code behind programmatically, as shown in Example 6-14.

Example 6-14. Binding to an object declared in XAML
<!-- Window1.xaml -->
<Window ... xmlns:local="clr-namespace:WithBinding">
  <Window.Resources>
    <local:Person x:Key="Tom" Name="Tom" Age="11" />
  </Window.Resources>
  <Grid DataContext="{StaticResource Tom}">
    ...
    <TextBlock ...>Name:</TextBlock>
    <TextBox ... Text="{Binding Path=Name}" />
    <TextBlock ...>Age:</TextBlock>
    <TextBox ... Text="{Binding Path=Age}" />
    <Button ... Name="birthdayButton">Birthday</Button>
  </Grid>
</Window>

Now that we’ve moved the creation of the Person object to the XAML, we have to update our Birthday button Click handler from using a member variable to using the data defined in the resource (see Example 6-15).

Example 6-15. Using an object bound in XAML
public partial class Window1 : Window {
  ...
  void birthdayButton_Click(object sender, RoutedEventArgs e) {
     // Get the Person from the Window's resources
    Person person = (Person)this.FindResource("Tom");

    ++person.Age;
    MessageBox.Show(...);
  }
}

In Example 6-15, we’re using the FindResource method (introduced in Chapter 1 and detailed in Chapter 12) to pull the Person object from the main window’s resources. With this minor change, the result is brought again into parity with Figure 6-6.

Tip

In practice, I haven’t found data islands as described here to be useful for much beyond sample data. However, the facilities of XAML that allow it to produce graphs of arbitrary objects have a great number of uses beyond WPF.

Explicit Data Source

Once you’ve got yourself a named source of data, you can be explicit in the XAML about the source in the binding instead of relying on implicitly binding to a DataContext property set somewhere in the tree. Being explicit is useful if you’ve got more than one source of data (e.g., two Person objects). Setting the source explicitly is accomplished with the Source property in the binding, as shown in Example 6-16.

Example 6-16. Data binding using the Source property
<!-- Window1.xaml -->
<Window ...>
  <Window.Resources>
    <local:Person x:Key="Tom" ... />
    <local:Person x:Key="John" ... />
  </Window.Resources>
  <Grid>
    ...
    <TextBox Name="tomTextBox"
      Text="
        {Binding
          Path=Name,
          Source={StaticResource Tom}}" />

    <TextBox Name="johnTextBox"
      Text="
        {Binding
          Path=Name,
          Source={StaticResource John}}" />
    ...
  </Grid>
</Window>

In Example 6-16, we’ve bound two text boxes to two different Person objects, setting the Source property of the Binding object to each person explicitly.

Binding to Other Controls

As another example of using explicit data sources, WPF provides for binding one element’s property to another element’s property. For instance, if we wanted to synchronize the brush used to draw the Birthday button’s text with the foreground brush of the age text box (this will be handy later when we change the age text box’s color based on the person’s age), we can use the ElementName property of the Binding object, as shown in Example 6-17.

Example 6-17. Binding to another UI element
<TextBox Name="ageTextBox" Foreground="Red" ... />

<!-- keep button's foreground brush in sync w/ age text box's -->
<Button ...
  Foreground="{Binding Path=Foreground, ElementName=ageTextBox}"
  >Birthday</Button>

Now, no matter what means we use to change the foreground brush’s color of the age text box—via binding, code, or triggers (as we’ll see in Chapter 8)—the button’s foreground brush will always follow.

Value Conversion

In Example 6-17, we’ve bound the foreground brush of the Birthday button to whatever the foreground brush is for the age text box, but our text box never changes color, so neither will the Birthday button. However, we might decide that anyone over age 25 is hot, so should be marked in the UI as red.[30] When someone ages at the click of the Birthday button, we want to keep the UI up-to-date, which means we’ve got ourselves a perfect candidate for data binding—something along the lines of Example 6-18.

Example 6-18. Binding to a non-Text property
<!-- Window1.xaml -->
<Window ...>
  <Grid>
    ...
    <TextBox
      Text="{Binding Path=Age}"
      Foreground="{Binding Path=Age, ...}"
      ...
    />
    ...
  </Grid>
</Window>

In Example 6-18, we’ve bound the age text box’s Text property to the Person object’s Age property, as we’ve already seen, but we’re also binding the Foreground property of the text box to the same property on the Person object. As Tom’s age changes, we want to update the foreground color of the age text box. However, because the Age is of type Int32 and Foreground is of type Brush, a mapping from Int32 to Brush needs to be applied to the data binding from Age to Foreground. That’s the job of a value converter.

A value converter (or just “converter” for short) is an implementation of the IValueConverter interface, which contains two methods: Convert and ConvertBack.

The Convert method is called when converting from the source data to the target UI data (e.g., from Int32 to Brush). The ConvertBack method is called to convert back from the UI data to the source data. In both cases, the current value and the type wanted for the converted data are passed to the method.

To convert an Age Int32 into a Foreground Brush, we can implement whatever mapping in the Convert function we feel comfortable with (see Example 6-19).

Example 6-19. A simple value converter
[ValueConversion(/*sourceType*/ typeof(int), /*targetType*/ typeof(Brush))]
public class AgeToForegroundConverter : IValueConverter {

  // Called when converting the Age to a Foreground brush
  public object Convert(object value, Type targetType, ...) {
    // Only convert to brushes...
    if( targetType != typeof(Brush) ) { return null; }

    // DANGER! After 25, it's all downhill...
    int age = int.Parse(value.ToString(  ));
    return (age > 25 ? Brushes.Red : Brushes.Black);
  }

  public object ConvertBack(object value, Type targetType, ...) {
    // Should not be called in our example
    throw new NotImplementedException(  );
  }
}

In Example 6-19, in addition to deriving from IValueConverter, we’ve also applied the optional ValueConversion attribute. The ValueConversion attribute is useful for documenting the expected source and target types for developers and tools, but it is not enforced by WPF, so don’t expect it to catch values that don’t match the source or target types. The part that is required for our example is the implementation of Convert, where we hand out the brush that’s appropriate for the age being displayed. Because we haven’t provided any facility to change the Foreground brush being used to display the age, there’s no reason to do anything useful in the ConvertBack method—it won’t be called.

Tip

I chose the name AgeToForegroundConverter because I have specific semantics I’m building into my converter class that go above simply converting an Int32 to a Brush. Even though this converter could be plugged in anywhere that converted an Int32 to a Brush, I might have very different requirements for a HeightToBackgroundConverter, for example.

Once you’ve got a converter class, it’s easy to create an instance of one in the XAML, just like we’ve been doing with our Person object (see Example 6-20).

Example 6-20. Binding with a value converter
<!-- Window1.xaml -->
<Window ... xmlns:local="clr-namespace:WithBinding">
  <Window.Resources>
    <local:Person x:Key="Tom" ... />
    <local:AgeToForegroundConverter x:Key="ageConverter" />
  </Window.Resources>
  <Grid DataContext="{StaticResource Tom}">
    ...
    <TextBox
      Text="{Binding Path=Age}"
      Foreground="
        {Binding
          Path=Age,
          Converter={StaticResource ageConverter}}"
      ... />
    ...
  <Button ...
    Foreground="{Binding Path=Foreground, ElementName=ageTextBox}"
    >Birthday</Button>
  </Grid>
</Window>

In Example 6-20, once we have a named converter object in our XAML, we establish it as the converter between the Age property and the Foreground brush by setting the Converter property of the binding object.Figure 6-10 shows the result of our conversion.

A value converter in action ()
Figure 6-10. A value converter in action (Figure F-3)

In Figure 6-10, notice that as Tom’s age increases past the threshold, the converter switches the foreground brush from black to red. This change happens when the Age property changes. Because WPF detects the change, you do not need any explicit code to force the color change, just as with any other kind of data binding. Notice also that the foreground color of the Birthday button matches the age text box’s color, because we’re using element binding to keep them in sync.

Editable Value Conversion

In addition to value conversion from the underlying data type to some other type for display, like our age-to-foreground-brush converter, you may also use value conversion for editing convenience. For example, although the Age property is automatically converted for us between an Int32 and a String in base 10, maybe your users would prefer base 16 (who wouldn’t?!). Enabling editing in base 16 is a matter of converting to and from a string in hexadecimal format, as shown in Example 6-21.

Example 6-21. A value converter for integers in base 16
public class Base16Converter : IValueConverter {
  public object Convert(
    object value, Type targetType, ...) {
    // Convert to base 16
    return ((int)value).ToString("x");
  }

  public object ConvertBack(
    object value, Type targetType, ...) {
    // Convert from base 16
    return int.Parse(
      (string)value, System.Globalization.NumberStyles.HexNumber);
  }
}

Hooking up this value converter works just like before, but this time we’re converting the Text property of the TextBox instead of the Foreground property:

<TextBox ...
  Text="
    {Binding
      Path=Age,
      Converter={StaticResource base16Converter}}" />

Figure 6-11 shows the base-16 converter in action.

The base-16 value converter in action
Figure 6-11. The base-16 value converter in action

One thing you’ll notice in our Base16Converter implementation of IValueConverter is that we haven’t guarded against a user entering something that can’t be interpreted as a hexadecimal number. If he does, the resulting exception is not handled by WPF, but is instead shown to the user as an unhandled exception. Although you can spend your time writing code to catch conversion errors, what you’ll really like to do is catch those errors before they ever get to the value converter, and instead communicate them to your users. For that, you’ll be best served by validation rules.

Validation

A validation rule is some code for validating a piece of data in the target before it’s used to update the source. The validation code is realized as an instance of a class that derives from the base ValidationRule class (from the System.Windows.Controls namespace) and overrides the Validate method. A built-in validation rule called ExceptionValidationRule (see Example 6-22) provides some measure of protection against a user intent on entering data outside the range supported by our age-to-foreground value converter.

Example 6-22. Hooking up a validation rule
<Window ... xmlns:local="clr-namespace:WithBinding">
  <Window.Resources>
    ...
      <local:AgeToForegroundConverter x:Key="ageConverter" />
    </Window.Resources>
  ...
  <TextBox ...
    Foreground="
      {Binding
        Path=Age,
        Converter={StaticResource ageConverter}}">

  <TextBox.Text>
    <Binding Path="Age">
      <Binding.ValidationRules>
        <ExceptionValidationRule />
      </Binding.ValidationRules>
    </Binding>
  </TextBox.Text>

</TextBox>

In Example 6-22, we’re using the shortcut markup extension binding syntax to bind the Foreground property to the Age (via the age-to-foreground value converter), but using the longhand syntax to bind the Text property to the Age so that we can create a list of validation rules. These validation rules will be executed in order when the target property changes. If they all succeed, the object is updated and everyone’s happy. If one of the rules fails, WPF highlights the offending data to make it easy to see what to fix, as shown in Figure 6-12.

A TextBox control highlighted as invalid ()
Figure 6-12. A TextBox control highlighted as invalid (Figure F-4)

As nifty as the red outline around the offending text box is, it still doesn’t let the user know what’s wrong (i.e., the error message associated with the exception isn’t shown). To do that, we need to look under the hood a bit.

When a validation result indicates invalid data, a ValidationError object is created that contains an object meant to describe the error, ideally for display by the UI. In the case of the ExceptionValidationRule, this “error content” object contains the Message property of the Exception the validation rule catches. To gain access to those errors, you can listen to the ValidationError attached event, which you can set up as shown in Example 6-23.

Example 6-23. Handling the ValidationError event with a message box
// Window1.cs
...
public Window1(  ) {
  InitializeComponent(  );

  this.birthdayButton.Click += birthdayButton_Click;

  // Listen for the validation error event on the age text box
  // (you can do this in XAML by handling the Validation.Error
  //  attached event on the ageTextBox)
  Validation.AddErrorHandler(this.ageTextBox,
    ageTextBox_ValidationError);
}

void ageTextBox_ValidationError(
  object sender, ValidationErrorEventArgs e) {

  // Show the string pulled out of the exception by the
  // ExceptionValidationRule
  MessageBox.Show(
    (string)e.Error.ErrorContent, "Validation Error");
}
...
    <!-- Window1.xaml -->
    ...
    <TextBox Name="ageTextBox" ...>
      <TextBox.Text>
        <Binding Path="Age" NotifyOnValidationError="True">
          <Binding.ValidationRules>
            <ExceptionValidationRule />
          </Binding.ValidationRules>
        </Binding>
      </TextBox.Text>
    </TextBox>
    ...

In Example 6-23, we’re calling the static AddErrorHandler method on the Validation class so that when a validation event happens on the age text box, we’ll get a notification. In that event handler, we can access the Error.ErrorContent property to get to the string provided by the validation rule. This event fires, however, only if the NotifyOnValidationError property is set to True on the Binding (the default is False).

With this event handler in place, we get our message box when there’s a validation error, as shown in Figure 6-13.

Handling the ValidationError event by showing a message box
Figure 6-13. Handling the ValidationError event by showing a message box

And although “Input string was not in a correct format” is the message in the exception that the Parse method of the Int32 class throws when there’s a parse error, I think we can do better—especially if we’d also like to set a range on the numbers that our users can enter for age.[31]

Custom validation rules

To make sure that our person’s age is within a certain range, we simply derive from the ValidationRule class and override the Validate method, as shown in Example 6-24.

Example 6-24. A custom validation rule
public class NumberRangeRule : ValidationRule{
  int min;
  public int Min {
    get { return min; }
    set { min = value; }
  }

  int max;
  public int Max {
    get { return max; }
    set { max = value; }
  }
  public override ValidationResult Validate(
    object value, System.Globalization.CultureInfo cultureInfo) {
    int number;
    if( !int.TryParse((string)value, out number) ) {
      return new ValidationResult(
        false,
        "Invalid number format");

    }

    if( number < min || number > max ) {
      return new ValidationResult(
        false,
        string.Format("Number out of range ({0}-{1})", min, max));
    }

    //return new ValidationResult(true, null); // valid result
    return ValidationResult.ValidResult; // static valid result
                                         // to save on garbage
  }
}

In this case, we’re creating a custom class with two public properties that describe the valid range of a number (specifically, an integer). The result of the validation is always an instance of the ValidationResult class. The most important part of the ValidationResult is the first argument to the constructor, which indicates whether the data is valid (true) or invalid (false). After that, we’re free to pass whatever we want as a CLR object. In our example, we check whether the string can be parsed into an integer and is within our range, passing back False and an error string if it’s not. Otherwise, we pass back True. (Because a valid result has little need for error detail, the ValidationResult class provides the static ValidResult property—a ValidationResult constructed by passing True and null—which you should use instead of creating a new ValidationResult object for a valid result.)

To hook up our validation rule, we put it to the Binding object’s ValidationRules collection instead of the ExceptionValidationRule, as shown in Example 6-25.

Example 6-25. Hooking up a custom validation rule
<TextBox ...
  Foreground="
   {Binding
     Path=Age,
     Converter={StaticResource ageConverter}}">
  <TextBox.Text>
    <Binding Path="Age">
      <Binding.ValidationRules>
        <local:NumberRangeRule Min="0" Max="128" />
      </Binding.ValidationRules>
    </Binding>
  </TextBox.Text>
</TextBox>

Now, when there’s a problem with the data, we get a message such as those shown in Figure 6-14 and Figure 6-15.

A validation error from a custom validation rule
Figure 6-14. A validation error from a custom validation rule
Another validation error from a custom validation rule
Figure 6-15. Another validation error from a custom validation rule

And so, although we now have nicer, more meaningful messages for our user when he enters invalid data, I am not a fan of the message box for validation error reporting (it stops the very activity you’re trying to enable). Instead, I prefer a tool tip, as in Example 6-26.

Example 6-26. Handling the ValidationError event with a tool tip
void ageTextBox_ValidationError(
  object sender, ValidationErrorEventArgs e) {

  // Show the string created in NumberRangeRule.Validate
  ageTextBox.ToolTip = (string)e.Error.ErrorContent;
}

At first, this code works just peachy keen, as shown in Figure 6-12.

Handling the ValidationError event by setting a tool tip
Figure 6-16. Handling the ValidationError event by setting a tool tip

When there’s a validation error, the message is shown in the tool tip on the control that’s holding invalid data. The problem is, once the user has corrected the data, the tool tip continues to hang around, as shown in Figure 6-17.

The tool tip hanging around after the validation error has been resolved
Figure 6-17. The tool tip hanging around after the validation error has been resolved

Unfortunately, there’s no ValidationSuccess event that lets us clear the error message from the tool tip. What we really want is to update the tool tip based on the changing validation error data, whether it’s in error or success, which sounds like a job for data binding. However, before we can do that, we need to take a closer look at Path syntax.

Binding Path Syntax

When you use Path=Somethingin a Binding statement, the Something can be in a number of formats, including the following commonly used variants:[32]

Path=Property

Bind to the property of the current object, whether the property is a CLR property, a dependency property, or an attached property (e.g., Path=Age).

Path=(OwnerType.AttachedProperty)

Bind to an attached dependency property (e.g., Path=(Validation.HasError)).

Path=Property.SubProperty

Bind to a subproperty (or a sub-subproperty, etc.) of the current object (e.g., Path=Name.Length).

Path=Property[n]

Bind to an indexer (e.g., Path=Names[0]).

Path=Property/Property

Master-detail binding, described later (e.g., Path=Customers/Orders).

Path=(OwnerType.AttachedProperty)[n].SubProperty

Bind to a mixture of properties, subproperties, and indexers (e.g., Path=(Validation.Errors)[0].ErrorContent).

So far, we’ve been using the Path=Property syntax, but if we want to get at an error on the validation errors collection, we’ll need to use a mixed path that includes an attached property, an indexer, and a subproperty, as shown in Example 6-27.

Example 6-27. Binding the ToolTip property to the validation error message
<TextBox
  Name="ageTextBox" ...
  ToolTip="{Binding
             ElementName=ageTextBox,
             Path=(Validation.Errors)[0].ErrorContent}">
  <TextBox.Text>
    <Binding Path="Age">
      <!-- No need for NotifyOnValidationError="true" -->
      <Binding.ValidationRules>
        <local:NumberRangeRule Min="0" Max="128" />
      </Binding.ValidationRules>
    </Binding>
  </TextBox.Text>
</TextBox>

In Example 6-27, the tool tip has been bound to the first error from the attached property Errors collection. When there are no errors, the tool tip is empty. When there is an error, the ErrorContent property (which, you’ll recall, we pack with an error string in NumberRangeRule.Validate) is used to populate the tool tip. We no longer need to set the NotifyOnValidationError property or handle the ValidationError event because as the Errors collection changes, the binding makes sure that the tool tip is kept up-to-date. In other words, we use data binding to the ToolTip property on the age text box to report a validation error on the Text property. When the collection of errors is null, the binding engine will automatically null out the tool tip, giving us the empty tool tip on success that we so deeply desire.

Relative Sources

One thing you may find a bit onerous in Example 6-27 is the use of the explicit ElementName to bind to another part of the target as the data source. Wouldn’t it be nicer if you could just say, “Bind to myself, please?” And in some cases, you may not have a name for the thing to which you’d like to bind (e.g., to fulfill queries like “Bind to the Border that’s the parent or grandparent [or great-grandparent] of me” or even “Bind to the previous bit of data in the list instead of the current bit of data”). All of these are available with the use of the RelativeSource property of a binding, shown in Example 6-28.

Example 6-28. Using a RelativeSource
<TextBox ...
  ToolTip="{Binding RelativeSource={RelativeSource Self},
                    Path=(Validation.Errors)[0].ErrorContent}">

In Example 6-28, we’re using the Self designator to use the TextBox currently serving as the data binding UI target as the data source, so that we can bind to the validation errors collection associated with it to compose the tool tip. For more information about Self and the other relative sources—FindAncestor, Previous, and TemplatedParent (which is also discussed in Chapter 9)—I recommend the SDK documentation.[33]

Update Source Trigger

If you’ve been following along, you may have noticed that validation, and therefore the pushing of the updated data into the underlying object, doesn’t happen until the age text box loses focus. On the other hand, you may decide that you’d like validation et al. to happen immediately when the control state changes, long before the focus is lost. This behavior is governed by the UpdateSourceTrigger property on the Binding object:

namespace System.Windows.Data {
  public enum UpdateSourceTrigger {
    Default = 0, // updates "naturally" based on the target control
    PropertyChanged = 1, // updates the source immediately
    LostFocus = 2, // updates the source when focus changes
    Explicit = 3, // must call BindingExpression.UpdateSource(  )
  }
}

The desfault value of UpdateSourceTrigger is UpdateSourceTrigger.Default, which means that the trigger for updating the data source is based on the target property (e.g., the trigger for the Text property of the TextBox is LostFocus). If you’d like to force another kind of behavior, you can set it on the Binding, as shown in Example 6-29.

Example 6-29. Changing the update source trigger
<TextBox ...>
  <TextBox.Text>
    <Binding Path="Age" UpdateSourceTrigger="PropertyChanged">
      ...
    </Binding>
  </TextBox.Text>
</TextBox>

In this case, instead of waiting for the focus to be lost to do validation, it happens on each character entered.

Debugging Data Binding

You may have noticed that our age text box’s binding options have gotten fairly involved:

<TextBox ...
  Foreground="{Binding Path=Age,
                       Source={StaticResource Tom},
                       Converter={StaticResource ageConverter}}"
  ToolTip="{Binding RelativeSource={RelativeSource Self},
                    Path=(Validation.Errors)[0].ErrorContent}">
  <TextBox.Text>
    <Binding Path="Age" UpdateSourceTrigger="PropertyChanged">
      <Binding.ValidationRules>
        <local:NumberRangeRule Min="0" Max="128" />
      </Binding.ValidationRules>
    </Binding>
  </TextBox.Text>
</TextBox>

There’s a lot going on here and it would be easy to get some of it wrong. For example, if we had a background in journalism, we might have used one-based indexing instead of zero-based indexing to access the first error in our list of validation errors when setting up the binding for the tool tip:

<TextBox ...
  ToolTip="{Binding RelativeSource={RelativeSource Self},
                    Path=(Validation.Errors)[1].ErrorContent}">
  ...
</TextBox>

In this case, as in most others, the WPF data binding engine will simply swallow the error so as not to disturb our user friends.[34] So, how are we to find it? Well, you need only check the debug output to see the error shown in Example 6-30, and all will be revealed.

Example 6-30. Watch debug output for help debugging data binding problems
System.Windows.Data Error: 12 : Cannot get '' value (type 'ValidationError') from
'(Validation.Errors)' (type 'ReadOnlyObservableCollection'1')
BindingExpression:Path=(0).[1].ErrorContent; DataItem='TextBox'
(Name='ageTextBox'), target element is 'TextBox' (Name='ageTextBox'),
target property is 'ToolTip' (type 'Object') TargetInvocationException:
'System.Reflection.TargetInvocationException: Exception has been thrown by
the target of
an invocation. --->
System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative
and less than the size of the collection.
Parameter name: index

In this case, we can see that the index is out of range, giving us a clue as to how to fix it. The data binding debug output provides all kinds of helpful hints like this, and you should check it if eyeballing your data binding expressions doesn’t yield the source of the issue.[35]

Where Are We?

Data binding is about keeping two values synchronized. One value, the target, is a dependency property, typically on a UI element. The other, the source, is a CLR property—the result of an XPath expression, a dependency property, or a dynamic property used by objects like those provided by ADO.NET that don’t know what the data is going to be until runtime. By default, as either the target or the source changes, the other value is updated, but you can control that with the alternate binding modes (e.g., one-way, one-time, etc.). As data changes, type conversion happens automatically if a converter is available, although you can take full control of the conversion and validation process if you so choose, doing things like restricting data ranges and converting data formats, or even automatically showing errors in tool tips. You might think that WPF data binding is powerful with these features, and you’d be right, but we’ve just touched on the bare essentials associated with bindings to properties on a single object. When you’ve got a list of objects as your data source, you’ve got all kinds of other facilities, which is the subject of the next chapter.



[26] * Or XPath property, if your data is XML, which is discussed in Chapter 7.

[27] * WPF data binding sources can also expose data via implementations of ICustomTypeDescriptor, which is how ADO.NET’s data sources are supported.

[28] Actually, data binding doesn’t do any searching at runtime. Instead, it relies on the fact that the DataContext property is inheritable, which means that the WPF property system itself implements the scoping/searching behavior described here. (Inheritable dependency properties are described in Chapter 18.)

[29] * If you want to get fancy, you can create a TypeConverter that can accept a string as input or a markup extension as well, but generally the default constructor route is the easiest way to provide XAML support for your custom types.

[30] * Or, anyone over 25 is in more danger of dying and red means “danger”—whichever makes you more likely to recommend this book to your friends . . .

[31] * On August 4, 1997, the world’s oldest person so far, Jeanne Louise Calment, died at age 122, having taken up fencing at age 85 and outlived the holder of her reverse-mortgage. Although I firmly believe that Ms. Calment is showing us the way to a richer, longer life, it’ll be a while yet before we need the full range supported by the Int32 class (2,147,483,647 years young).

[32] * The Windows Platform SDK has a more complete list of the WPF binding path syntax variants, including escaping rules, on a page titled “Binding Declarations Overview,” available at http://msdn2.microsoft.com/en-us/library/ms752300.aspx#path_syntax(http://tinysells.com/65).

[33] * A good place to continue your exploration of relative sources is the “RelativeSourceMode Enumeration” page in the Windows Platform SDK, which is available at http://msdn2.microsoft.com/en-us/library/system.windows.data.relativesourcemode.aspx(http://tinysells.com/66).

[34] * The swallowing of errors like these lets us declare data bindings before the data is actually available, simplifying our programming chores considerably in this area.

[35] * For more on the data binding debug output, see the SDK documentation for the PresentationTraceSources class at http://msdn2.microsoft.com/en-us/library/system.diagnostics.presentationtracesources.aspx or http://tinysells.com/79 and Mike Hillberg’s most excellent blog posting on this subject at http://blogs.msdn.com/mikehillberg/archive/2006/09/14/wpftracesources.aspx or http://tinysells.com/78.

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

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