Data Binding – Data Conversion

 

One of the more interesting topics that arise in Data-binding is that of Data Conversion. So much can be accomplished without explicit conversion that it is easy to overlook… until you hit the real world.

To see this, I’ll make a small modification to the code I wrote  for the Silverlight Tutorial on Data Binding. The premise of this simplified example is that you are interacting with a bookstore or a library, displaying information about one book at a time.

We assume that you get the book information from wherever it is held via a web service,  but that you will create Business Objects from that data, and it is these business objects that you will bind to (this is not the only way to do it, but it is the most flexible).

Briefly, the architecture in the original example looked like this

DataBindingArchitecture

What we’re trying to accomplish here: bind the data from a Book object to a Silverlight control (textBlock, etc.)

The Book class  is the business object. It is presumably filled from a call to a web-service, and the properties of the book are bound to the UI objects.  As discussed yesterday this is accomplished by setting the binding in the Xaml,

<TextBlock x:Name=”Title”
    Text=”{ Binding Title,  Mode=OneWay }”  />

The value that is bound, Title, is a property of the Book class

   1: public class Book : INotifyPropertyChanged
   2: {
   3:    private string bookTitle;
   4:     //...
   5:  
   6:    public string Title
   7:    {
   8:       get { return bookTitle; }
   9:       set
  10:       {
  11:          bookTitle = value;
  12:          NotifyPropertyChanged( "Title" );
  13:       }       // end set
  14:    }          // end property
  15:    //...
  16:  

 

In this case the binding target is a TextBlock also named Title. 

The Mode on the Binding is set to OneWay indicating that the Title will come from the BindingSource (the Book object) to be bound to the TextBlock, but changes made in the UI will not be sent back to the Binding Source.

<TextBlock x:Name=”Title”
    Text=”{ Binding Title,  Mode=OneWay }”  />

Converting Data – Dates for a Calendar

If the BookObject had a property for publication date that was of type DateTime,

public DateTime PublicationDate
{
   get {//…}
   set {//…}
}

You could easily bind it to a Calendar object,

<basics:Calendar x:Name=”pubDateCal”
    IsTodayHighlighted=”False” 
     SelectedDate=”{ Binding PublicationDate }”
     DisplayDate= “{ Binding PublicationDate }”
     /> 

… A calendar control expects a DateTime for its Selected and Display date Dependency Properties. But what do you do if the service you are using does not provide that for you. What if, instead, it provides only a month and a year and yet, you’d still like to show the calendar?

You have a number of options. Certainly, you could have the Book convert the month and year into a DateTime before the binding, creating a PublicationDate property that returns a DateTime ready to be bound.

An alternative is to create a converter that takes a month and a year and converts it to the type needed by your control when displaying it, and converts it back when storing data to the business object.

Okay, to make the contrivance just a bit worse, Converters are happier taking a single type and converting to another type, so we’re going to have the Book class maintain its publication Month and Year as integers, but I’ve taken the liberty™ of having the PublicationDate property return a new class MonthAndYear that we can then convert to DateTime.

This is either due to spec requirements beyond the scope of this article, or to my wanting a simple example that has now gotten completely out of hand.

In any case, here is the class we’ll use:

   1: public class MonthAndYear
   2: {
   3:    public int Month { get; set; }
   4:    public int Year { get; set; }
   5:    public MonthAndYear( int m, int y )
   6:    {
   7:       Month = m;
   8:       Year = y;
   9:    }
  10: }

Value Converters have real value

You can imagine many plausible situations where you might decide that your business object has data in a form that does need conversion and yet the business class ought not be doing that conversion. It is not unreasonable to delegate that responsibility to the UI level if it is only the UI level that needs the conversion.

Silverlight provides an Interface IValueConverter, which is designed for this situation. The interface requires that you implement the Convert and the ConvertBack methods, and our architecture becomes more like this,

DataBindingArchitectureExtended 

The Book class is modified to add the members but this time they do not each have their own property, but rather there is a single property, PublicationDate that manages them via the creation or reception of a MonthAndYear object,

   1: public class Book : INotifyPropertyChanged
   2: {
   3:    private string bookTitle;
   4:    private int pubMonth;
   5:    private int pubYear;
   6:    // other members
   7:  
   8:    public MonthAndYear PublicationDate
   9:    {
  10:       get { return new MonthAndYear( pubMonth, pubYear ); }
  11:       set
  12:       {
  13:          pubMonth = value.Month;
  14:          pubYear = value.Year;
  15:          NotifyPropertyChanged( "PublicationDate" );
  16:       }
  17:    }
  18:  

This greatly simplifies matters as we can not convert from one type (MonthAndYear) to another DateTime (and back, if we like).

Here is the complete DateToMonthAndYear.cs

   1: using System;
   2: using System.Windows.Data;
   3:  
   4: namespace DataBinding
   5: {
   6:    public class DateToMonthYear : IValueConverter
   7:    {
   8:  
   9:       public object Convert( 
  10:          object value,           // source data
  11:          Type targetType,        // type of dataa expected by target d.p.
  12:          object parameter,       // optional param to help conversion
  13:          System.Globalization.CultureInfo culture )
  14:       {
  15:          MonthAndYear temp = value as MonthAndYear;
  16:          if ( temp != null )
  17:          {
  18:             return new DateTime( temp.Year, temp.Month, 1 );
  19:          }
  20:          else
  21:          {
  22:             throw new ArgumentException( "DateToMonthYear conversion failed," + 
  23:                 "trying to convert " + value.ToString() );
  24:          }
  25:                
  26:       }
  27:  
  28:  
  29:       public object ConvertBack( 
  30:          object value, 
  31:          Type targetType, 
  32:          object parameter, 
  33:          System.Globalization.CultureInfo culture )
  34:       {
  35:          MonthAndYear retVal = null;
  36:          try
  37:          {
  38:             DateTime dt = (DateTime) value;
  39:             retVal =  new MonthAndYear( dt.Month, dt.Year );
  40:          }
  41:          catch ( Exception e )
  42:          {
  43:             throw new ArgumentException( "Exception thrown in DateToMonthYear.ConvertBack. Value = " + 
  44:                value.ToString(), e );
  45:          }
  46:          return retVal;
  47:       }
  48:    }
  49: }

 

 

What we’re trying to accomplish: The book class now has a property, PublicationDate that returns a MonthAndYear, we want to bind that to a Calendar that expects a DateTime object. The Converter will make the conversion back and forth.

We modify Page.xaml like this,

   1: <UserControl x:Class="DataBinding.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   2:     xmlns:local="clr-namespace:DataBinding"  Width="433" Height="416" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
   3:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" 
   4:     xmlns:basics="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls">
   5:     <UserControl.Resources>
   6:         <local:DateToMonthYear x:Key="Converter1" />
   7:     </UserControl.Resources>
   8:         <Grid x:Name="LayoutRoot" Background="White">
   9:             <!-- Snip! -->
  10:         <basics:Calendar x:Name="pubDateCal"
  11:             FontFamily="Georgia" 
  12:             Grid.Column="1" Grid.Row="5" Grid.RowSpan="3" 
  13:             IsTodayHighlighted="False" 
  14:             VerticalAlignment="Top" HorizontalAlignment="Left"
  15:              SelectedDate="{ Binding PublicationDate, Mode=OneWay, 
  16:                                 Converter={StaticResource Converter1} }"
  17:              DisplayDate= "{ Binding PublicationDate, Mode=OneWay, 
  18:                                 Converter={StaticResource Converter1} }"     />        
  19:                         
  20:     </Grid>
  21: </UserControl>

We create two new namespaces. On line 4 basics is created to bring in the System.Windows.Controls namespace and is used on line 10 to create the Calendar.

On line 2 local is created to create a namespace for the application itself.

On lines 5 through 7 a Resources section is created, inside of which a resource of type DateToMonthYear is declared using the local namespace; this ties the resource to the class we created, and assigns it a static resource key of converter1.

The SelectionDate and the DisplayDate of the Calendar control are bound to the PublicationDate and given the Converter to use, identified using the resource by that key (converter1) on lines 16 and 18. 

Putting It Together

When the Binding engine tries to bind PublicationDate to the SelectedDate or DisplayDay Dependency Properties of the Calendar, it will see that the converter is referencing the staticResource whose key is Converter1. It will look at the resource section and see that the key resolves to a namespace that identifies which assembly to look in, and an identifier that tells the Binding engine which class to use.  That class must implement IValueConverter, and it will call Convert, passing in the MonthAdnYear object and getting back a DateTime that it will then use to bind to the Dependency Properties.  Piece of cake.

The example is a bit contrived, but that allows for some simplicity in tracing it through. The source code is here.

Thanks.

-j

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in z Silverlight Archives and tagged . Bookmark the permalink.