Data binding in WPF – converters

In the last post, we saw how to use data binding to synchronize the value of a data field with the text displayed in a TextBox. In that example, the binding was relatively straightforward, since the type of data (an int) can be displayed as text in a TextBox, as WPF does an automatic conversion from a numeric type to text.

However, data binding is much more flexible than this. Pretty well any property of a control can be bound to any type of data. If WPF doesn’t provide an automatic conversion, though, you’ll need to write a converter yourself to fix the binding.

We’ll look at a couple of examples here. First, recall that the program we were working on in the last post allowed the user to step through the integers and test if each was either prime or perfect. The interface to the program looks like this:

The ‘Factors’ box displays the number of factors (excluding 1 and the number itself) that ‘Number’ has; ‘Sum of factors’ adds up Number’s factors (this time, including 1). If ‘Factors’ is zero, the number is prime, while if ‘Sum of factors’ equals Number, the number is perfect. We’d like to colour-code these two boxes so that Factors is LightSalmon if the number is prime, and Sum of factors is LightSeaGreen if the number is perfect. This requires binding a numerical value (in the case of a prime number) or a logical value (in the case of a perfect number) to a Brush used to paint the background of a TextBox. Not surprisingly, there is no built-in conversion between these types of data, so we’ll need to write one.

A data binding converter must be a class that implements the System.Windows.Data.IValueConverter interface. This interface requires us to write two methods: Convert() and ConvertBack(). Here’s the code for NumberToBackgroundConverter, which does the required conversion in our program:

  using System.Windows.Data;
  using System.Windows.Media;
  ......
  class NumberToBackgroundConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
      if (parameter.ToString().Equals("IsPerfect"))
      {
        bool isPerfect = bool.Parse(value.ToString());
        if (isPerfect)
        {
          return Brushes.LightSeaGreen;
        }
        else
        {
          return Brushes.Transparent;
        }
      }
      else if (parameter.ToString().Equals("NumFactors"))
      {
        int number = int.Parse(value.ToString());
        if (number == 0)
        {
          return Brushes.LightSalmon;
        }
        else
        {
          return Brushes.Transparent;
        }
      }
      return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
      throw new NotImplementedException();
    }
  }

Notice the two using statements at the start; they are required for the IValueConverter interface and the Brushes objects, respectively.

The Convert() method is the only one we need to implement, since we won’t be converting back from a Brush to a data value. The first argument to Convert(), ‘value’, is the object from which conversion is taking place, and the object returned from Convert() is the object to which conversion is done. These are both defined as object type, which means that a converter can convert any type of object to any other type (since ‘object’ is the base class of all C# classes).

The second argument, ‘targetType’, is the data type of the target, which is sometimes useful in deciding what object to generate as the return value. We don’t use it here. The third argument, ‘parameter’, is a parameter that can also be used to decide what type of conversion to do. In this example, the parameter is used to decide whether we’re dealing with a prime number or a perfect number. We’ll see how this parameter is specified in a minute, when we consider attaching the converter to a control’s property.

The final argument, ‘culture’, is used in globalization, as it allows things like date and time strings to be formatted appropriately for a given country. Again, we won’t be using this here.

The rest of the code is fairly obvious. We test to see if the parameter is “IsPerfect”. If so, ‘value’ will be a bool indicating whether the current number is perfect. If it is, we return a LightSeaGreen brush; if not, we return a Transparent brush so the regular background colour of the ‘Sum of factors’  TextBox shows through. The background colour of TextBoxes and other controls depends on the overall Windows theme the user has adopted, so we don’t want to anticipate the choice by specifying a particular colour.

The second part of Convert() deals with the prime number box. If parameter is “NumFactors”, ‘value’ will be an int, so we check to see if it’s zero. If so, we return a LightSalmon brush.

That’s it for the converter class. Now, how do we use it?

In Expression Blend (EB), select the sumFactorsTextBox. In its properties panel, find Background and click on the little square to the right. This will bring up a context menu, from which you select ‘Data binding’. Select the data field to act as the source in the same way as we did in the previous post when binding data to the text in a TextBox. In this case, we want to bind the background colour to the IsPerfect bool data field in the PrimePerfect class, so add that class to the Data Sources in the Data Field tab (if it’s not already there), and then select the IsPerfect field on the right. You will need to select ‘All properties’ in the show box at the lower right to make these data fields visible.

Next, expand the panel at the bottom by clicking on the downward pointing arrow. Click the … box to the right of ‘Value converter’ and select the NumberToBackgroundConverter class. Enter “IsPerfect” in the ‘Converter parameter’ box, then click OK. This completes the data binding, and if you run the program at this point, the Sum of factors box should turn green whenever you hit a perfect number (6 is the lowest one; 28 is the next).

The procedure for the ‘Factors’ box is just the same. This time, bind the Background to the NumFactors field in PrimePerfect. Use the same converter class, but give the parameter as “NumFactors”. Now both boxes should light up at the correct times as you step through the numbers.

As a final example, we would like the Dec button to become disabled if Number reaches 2, since we don’t want to look at any integers smaller than 2. To do this, we first write another converter class:

using System.Windows.Data;
......
  class NumberToEnabledConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
      int number = int.Parse(value.ToString());
      if (number <= 2)
      {
        return false;
      }
      else
      {
        return true;
      }
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
      throw new NotImplementedException();
    }
  }

This time the input ‘value’ is an integer and the return object is a bool (false if the button is disabled, true otherwise). We check if ‘value’ is <= 2 and disable the button if it is.

In EB, the binding proceeds just as before. Select decButton, find its IsEnabled property (in Common Properties in the Properties panel), bind this to the Number field in PrimePerfect and attach a converter, specifying the converter class as NumberToEnabledConverter. No parameter is needed this time. Now each time Number gets reduced to 2, Dec is disabled.

There is still one flaw in this program: it is possible to select the Number TextBox and type in a value less than 2 (in fact you can enter a negative number). You can even type in any old text, though if you do this and then hit tab to remove the focus from the TextBox, you’ll see a red outline appear around the box, indicating invalid input (which admittedly isn’t very user-friendly – what does the red box mean, and what is correct input?). To fix these problems, we need to investigate validation, but that’s a topic for another post.

Source code available here.

Advertisements
Post a comment or leave a trackback: Trackback URL.

Trackbacks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: