Part 25 - Advanced Binding with Value Converters

Download this episode

Download Video

Description

 Download the source code for this lesson at http://absolutebeginner.codeplex.com/

The last piece of the puzzle we’ll need before building our next complete exercise app is to understand Value Converters.  In short, sometimes you want to use data in your View Model that is not in the correct format or must be in some way modified before you can bind to it.  A Value Converter allows you to perform a conversion on the data to format the data, change the value of the data, perform calculations on the data and more.

In fact, in this lesson, I’ll demonstrate how diverse your value converters can be.

I’ve created a “Before” folder in Lesson25.zip.  Please make sure you download this and unzip it to a directory called BindingWithValueConverters. 

Here I have the beginnings of a weather app.  In this example, I’m randomly generating weather, however in a real app you would obviously be pulling weather from an online weather service.

Notice the structure of the classes that will form the data model.  First, there is a DayForecast defined as so:

public class DayForecast
{
  public DateTime Date { get; set; }
  public Weather Weather { get; set; }
  public ObservableCollection<TimeForecast> HourlyForecast { get; set; }
}

For a given day (the Date property) the Weather property will indicate the defining attribute of the day (whether sunny, cloudy, rainy or snowy … I’ve reduced the number of options for simplicity’s sake).  I’ve used an enum called Weather to denote the various values of Weather.  Finally, the HourlyForecast property is made up of an ObservableCollection<TimeForecast> … precisely 24 TimeForecast objects, to be exact, one for each hour of the day from 0 (midnight) to 11pm (hour 23) as indicated by the Hour property.  TimeForecast is defined as:

public class TimeForecast
{
  public int Hour { get; set; }
  public int Temperature { get; set; }
}

How will we create data for the Temperature of each TimeForecast object?  I’ve created a FiveDayForecast class which has a single method, GetForecast().  Its job is to deliver an ObservableCollection<DayForecast> to the MainPage.xaml.  This class also has a helper method called generateRAndomTimeForecast that creates 24 hours with a random temperature and returns those as an ObservableCollection<TimeForecast> for use in the DayForecast.HourlyForecast property:

private static ObservableCollection<TimeForecast> generateRandomTimeForecast(int seed)
{
  var random = new System.Random(seed);
  var forecast = new ObservableCollection<TimeForecast>();

  for (int i = 0; i < 24; i++)
  {
    var timeForecast = new TimeForecast();
    timeForecast.Hour = i;
    timeForecast.Temperature = random.Next(105);
    forecast.Add(timeForecast);
  }
  return forecast;
}

How will this data be displayed in our app?  If you run the Before app as is, you’ll see the layout of the User Interface.

clip_image002

Admittedly, most of the data is faked right now.  The data exists, however we’ve not bound to it because it’s not in the correct format for our purposes.

Let’s look at the XAML to understand what is happening and how we’ll need to plug our converted values into the existing app.

<Page
  . . .
  DataContext="{Binding WeatherViewModel, RelativeSource={RelativeSource Self}}">

<ScrollViewer>
  <StackPanel>
    <ListView ItemsSource="{Binding}">
      <ListView.ItemTemplate>
        <DataTemplate>
          <StackPanel Orientation="Vertical" Margin="20,0,0,30">
            <TextBlock Text="{Binding Date}" FontSize="24" />
            <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
              <Image Source="assets/snowy.png" Width="100" Height="100" />
              <StackPanel Orientation="Vertical" Margin="20,0,0,0">
                <TextBlock Text="High: " FontSize="14" />
                <TextBlock Text="55" FontSize="18" />
                <TextBlock Text="Low: " FontSize="14" />
                <TextBlock Text="32" FontSize="18" />
              </StackPanel>
            </StackPanel>
          </StackPanel>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </StackPanel>
</ScrollViewer>

As you can see, we’re employing a ScrollViewer to allow us to scroll past the bottom of the viewable area.  It wraps a StackPanel that contains a ListView.  In retrospect, a GridView may have worked as well if not better in this situation.  The ListView is bound to the ViewModel.  The ListView’s DataTemplate is made up of a series of StackPanels with TextBlocks and an Image.  The only data item we’re binding to at the moment is the Date, but even that is in need of a little massaging to make it look correct.  Ultimately, we’ll want to bind the source of the Image based on the Weater enum, and we’ll determine the high and low temperatures for a given day all using Value Converters.

I’ll add a new class to the project called DateToStringConverter.

First, I’ll make the class public and I’ll require it to implement IValueConverter.  Once I’ve added that code,

public class DateToStringConverter : IValueConverter
{
}

Next, with my text cursor on the term IValueConverter I’ll use the Command [dot] shortcut to display the Intellisense menu:

clip_image004

… which will allow me to choose to Implement the interface by hitting the [Enter] key on my keyboard.  My class now looks like this:

public class DateToStringConverter : IValueConverter
{

  public object Convert(object value, Type targetType, object parameter, string language)
  {
    throw new NotImplementedException();
  }

  public object ConvertBack(object value, Type targetType, object parameter, string language)
  {
    throw new NotImplementedException();
  }
}

As you can see, two method stubs are added to the class by implementing the IValueConverter.  Convert will perform the conversion to your target display value, ConvertBack will do the opposite.  In our case, we merely need to format the Date into a string with the format I desire, so I implement like so:

    public class DateToStringConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            DateTime date = (DateTime)value;

            return String.Format("{0:dddd} - {0:d}", date);
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            // For "display only", I don't bother implementing ConvertBack
            // HOWEVER if your planning on two-way binding, then you must.
            throw new NotImplementedException();
        }
    }

Note that a better / more complete example of a ValueConverter that takes into account the language / culture as well as the converter parameter is found here:

http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.data.ivalueconverter

Next, I’ll need to tell my MainPage.xaml to use this new converter (I’ll remove everything except what will be added or changed):

<Page
  . . .
  xmlns:valueconverter="using:ValueConverterExample"
  . . .>
  <Page.Resources>
    <valueconverter:DateToStringConverter x:Key="DateToStringConverter" />
  </Page.Resources>

  . . .

        <TextBlock Text="{Binding Date,
                   Converter={StaticResource DateToStringConverter}}"
                   FontSize="24" />
                          
In the Page declaration, I create a new namespace.  In this specific case I didn’t have to do this because the Value Converter I created is in the same namespace as the local namespace that’s already defined in the page.  However, at some point I plan on moving my Value Converters into a folder and will change their namespace accordingly.  Therefore, I’ve added this as a reminder / placeholder to myself.

Next, I add the Value Converter key “DateToStringConverter” and point it to the class named DateToStringConverter (inside the namespace that is aliased to “valueconverter”).

Third, I use the value converter in my TextBlock, inside the binding expression.

Testing the result provides the format we desire.

Next, I’ll add a value converter that will be used to change the Image’s source.  I’ll use the same basic procedure as before to create a new value converter called WeatherEnumToImagePathConverter.  A bit wordy, admittedly, but very descriptive.  I’ll implement it like so:

public class WeatherEnumToImagePathConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, string language)
  {
    var weather = (Weather)value;
    var path = String.Empty;

    switch (weather)
    {
      case Weather.Sunny:
        path = "assets/sunny.png";
        break;
      case Weather.Cloudy:
        path = "assets/cloudy.png";
        break;
      case Weather.Rainy:
        path = "assets/rainy.png";
        break;
      case Weather.Snowy:
        path = "assets/snowy.png";
        break;
      default:
        Break;
    }

    return path;
  }

  public object ConvertBack(object value, Type targetType, object parameter, string language)
  {
    throw new NotImplementedException();
  }
}

Hopefully you see a pattern.  I am merely taking the value, casting it to the original type I am expecting, then performing some logic or formatting on the value to determine the end result.  In this case, I’m returning the path to the image I’ll use in the Image’s source.

I’ll now add the code to the XAML to implement this:

. . .
<Page.Resources>
  . . .
  <valueconverter:WeatherEnumToImagePathConverter x:Key="WeatherEnumToImagePathConverter" />
</Page.Resources>

. . .
      <Image Source="{Binding Weather,
             Converter={StaticResource WeatherEnumToImagePathConverter}}"
             Width="100"
             Height="100" />

Running the app will display each of the icons at least once.

Next, I’ll determine the high and the low for each day.  Recall that each day’s forecast contains an ObservableCollection<TimeForecast> that represent the temperature for each hour.  All we need to do is look at that collection and find the highest number (or lowest number) and return those from our Value Converter.  A simple LINQ query will do the trick in each case.

I’ll add two more classes,

HourlyCollectionToDailyHighConverter
HourlyCollectionToDailyLowConverter

Here’s the HourlyCollectionToDailyHighConverter:

public class HourlyCollectionToDailyHighConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, string language)
  {
    var hourly = (ObservableCollection<TimeForecast>)value;

    return hourly
             .OrderByDescending(p => p.Temperature)
               .FirstOrDefault()
                 .Temperature;
        }
  . . .
}

And the HourlyCollectionToDailyLowConverter:

public class HourlyCollectionToDailyLowConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, string language)
  {
    var hourly = (ObservableCollection<TimeForecast>)value;

    return hourly
             .OrderBy(p => p.Temperature)
               .FirstOrDefault()
                 .Temperature;
  }

  public object ConvertBack(object value, Type targetType, object parameter, string language)
  {
    throw new NotImplementedException();
  }
}

And now I’ll add them to the XAML:

<Page.Resources>
. . .
  <valueconverter:HourlyCollectionToDailyHighConverter x:Key="HourlyCollectionToDailyHighConverter" />
  <valueconverter:HourlyCollectionToDailyLowConverter x:Key="HourlyCollectionToDailyLowConverter" />
</Page.Resources>

      <TextBlock Text="High: " FontSize="14" />
      <TextBlock Text="{Binding HourlyForecast,
                   Converter={StaticResource HourlyCollectionToDailyHighConverter}}"
                 FontSize="18" />
      <TextBlock Text="Low: " FontSize="14" />
      <TextBlock Text="{Binding HourlyForecast,
                   Converter={StaticResource HourlyCollectionToDailyLowConverter}}"
                 FontSize="18" />


Now, when you run the completed app, the results are essentially the same (from an aesthetic perspective) however the data is being pulled from our View Model and shaped appropriately into what we need for our View. 

Recap

Value Converters allow us to shape the data items of our View Model into the format need to correctly bind to our View.  You can merely format the data item, or change it completely.  We’ll use this technique as we build a complete example in the next lesson.

Embed

Format

Available formats for this video:

Actual format may change based on video formats available and browser capability.

    The Discussion

    Comments closed

    Comments have been closed since this content was published more than 30 days ago, but if you'd like to send us feedback you can Contact Us.