Skip to main content

C# - Binding Converters In WPF [Beginner]


I'm going to come right out and say it, binding converters are one of the nicest pieces of WPF I've run across so far. Simply put, they provide a translation between your binding source and destination. The most common use I've found is when I'm binding to user interface elements, because on many occasions I don't store something in a data member that can just be stuck on the screen. This tutorial will provide an introduction to what binding converters are and how to use them.

The example application I'm going to make today is going to be a traffic light. The graphics will all be written in XAML and will be bound to an object that represents the state of the light (green, yellow, and red).

Let's start off with the object. This is a very simple object and should be very straight forward.
public class TrafficLight : INotifyPropertyChanged
{
  /// <summary>
  /// Fired whenever a property changes.  Required for
  /// INotifyPropertyChanged interface.
  /// </summary>
  public event PropertyChangedEventHandler PropertyChanged;

  /// <summary>
  /// The current state of the traffic light
  /// </summary>
  private States _state;

  /// <summary>
  /// All possible states of the traffic light
  /// </summary>
  public enum States
  {
    GREEN,
    YELLOW,
    RED
  }

  /// <summary>
  /// Gets or sets the state of the traffic light
  /// </summary>
  public States State
  {
    get { return _state; }
    set
    {
      if (value != _state)
      {
        _state = value;
        if(PropertyChanged != null)
          PropertyChanged(this, new PropertyChangedEventArgs("State"));
      }
    }
  }
}
 
Above is a pretty basic class that will control the state of our traffic light. It implements the INotifyPropertyChanged interface, which is located in the System.ComponentModel namespace. This interface is used by WPF's data binding engine to know when a property changes so the binding can be updated. I think the rest is pretty self explanatory, so on to some XAML.
<UserControl x:Class="BindingConverterTutorial.TrafficLightControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Width="120" Height="350">
  <Grid>
    <Border Background="Yellow"
            CornerRadius="10"
            BorderBrush="Gray"
            BorderThickness="2">
      <StackPanel VerticalAlignment="Center">
        <StackPanel.Resources>
          <Style TargetType="{x:Type Ellipse}">
            <Setter Property="Width" Value="100" />
            <Setter Property="Height" Value="100" />
            <Setter Property="Fill" Value="LightGray" />
            <Setter Property="Stroke" Value="Gray" />
            <Setter Property="StrokeThickness" Value="2" />
            <Setter Property="Margin" Value="4" />
          </Style>
        </StackPanel.Resources>
        <Ellipse />
        <Ellipse />
        <Ellipse />
      </StackPanel>
    </Border>
  </Grid>
</UserControl>
 
Now if we slap that control on our Window:
<Window x:Class="BindingConverterTutorial.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:my="clr-namespace:BindingConverterTutorial"
    Title="Window1" Height="512" Width="512">
  <Grid>
    <my:TrafficLightControl />
  </Grid>
</Window>
 
We get something that looks like this:

Traffic Light Sample App

OK, so there's not much going on here. Let's modify our window to create a TrafficLight object and bind it to our control.
<Window x:Class="BindingConverterTutorial.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:my="clr-namespace:BindingConverterTutorial"
    Title="Window1" Height="512" Width="512">
  <Window.Resources>
    <my:TrafficLight State="Green" x:Key="myTrafficLight" />
  </Window.Resources>
  <Grid>
    <my:TrafficLightControl DataContext="{StaticResource myTrafficLight}" />
  </Grid>
</Window>
 
If you run this code, you still won't see a green traffic light. That's because we haven't set up the binding in the actual TrafficLightControl object. Let's look at that now.
<UserControl x:Class="BindingConverterTutorial.TrafficLightControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Width="120" Height="350">
  <Grid>
    <Border Background="Yellow"
            CornerRadius="10"
            BorderBrush="Gray"
            BorderThickness="2">
      <StackPanel VerticalAlignment="Center">
        <StackPanel.Resources>
          <Style TargetType="{x:Type Ellipse}">
            <Setter Property="Width" Value="100" />
            <Setter Property="Height" Value="100" />
            <Setter Property="Fill" Value="LightGray" />
            <Setter Property="Stroke" Value="Gray" />
            <Setter Property="StrokeThickness" Value="2" />
            <Setter Property="Margin" Value="4" />
          </Style>
        </StackPanel.Resources>
        <Ellipse Fill="{Binding State}" />
        <Ellipse Fill="{Binding State}" />
        <Ellipse Fill="{Binding State}" />
      </StackPanel>
    </Border>
  </Grid>
</UserControl>
 
Because we set the DataContext of the TrafficLightControl in the Window, we can use the Binding attributes to bind to properties of the TrafficLight object. In this case, I bound the Fill color of each ellipse to the state of the traffic light. As you can imagine this won't work, because we're trying to tell the binding engine to make an enum into a Brush. Needless to say, it doesn't know how to do this.

If you were to run this code in debug mode, you'd see some output in the console telling you that it did not know how to perform this binding.

System.Windows.Data Error: 1 : Cannot create default converter to perform 'one-way' conversions between types 'BindingConverterTutorial.TrafficLight+States' and 'System.Windows.Media.Brush'. Consider using Converter property of Binding. 

This is where the converters come in. A converter can be any class that implements IValueConverter. This interface requires that you implement two methods: Convert and ConvertBack. Here's the converter that I'm going to apply to each light:
[ValueConversion(typeof(TrafficLight.States), typeof(Brush))]
public class ColorConverter : IValueConverter
{
  public enum Lights
  {
    GREEN,
    YELLOW,
    RED
  }

  public object Convert(object value, Type targetType, 
      object parameter, CultureInfo culture)
  {
    TrafficLight.States state = (TrafficLight.States)value;
    Lights light = (Lights)Enum.Parse(typeof(Lights), (string)parameter);

    switch (state)
    {
      case TrafficLight.States.GREEN:
        if (light == Lights.GREEN)
          return new SolidColorBrush(Colors.Green);
        break;
      case TrafficLight.States.YELLOW:
        if (light == Lights.YELLOW)
          return new SolidColorBrush(Colors.Yellow);
        break;
      case TrafficLight.States.RED:
        if (light == Lights.RED)
          return new SolidColorBrush(Colors.Red);
        break;
    }

    return new SolidColorBrush(Colors.LightGray);
  }

  public object ConvertBack(object value, Type targetType, 
      object parameter, CultureInfo culture)
  {
    return null;
  }
}
 
The first thing you might notice is the ValueConversion attribute. This is not actually required, but it helps tell developer tools what type is going to be converted to what other type. The state of the light is passed in through the value parameter. The value, targetType, and culture are all automatically passed in by WPF's binding engine. The parameter is an optional object that I used to tell the converter which light is requesting the conversion. I have to convert it from a string since the XAML syntax I used sends the argument as a string. I then check the state and the light to determine what the color is supposed to be.

The ConvertBack function can be used to go in the opposite direction. So if you had a user interface that let the user select which light was lit, you could pass the brush back through the binding conversion and convert it back to a States enum. In this case, since the binding is one-way, we simply return null.

Now let's look at the XAML that uses this converter.
<UserControl x:Class="BindingConverterTutorial.TrafficLightControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Width="120" Height="350">
  <Grid>
    <Border Background="Yellow"
            CornerRadius="10"
            BorderBrush="Gray"
            BorderThickness="2">
      <StackPanel VerticalAlignment="Center">
        <StackPanel.Resources>
          <Style TargetType="{x:Type Ellipse}">
            <Setter Property="Width" Value="100" />
            <Setter Property="Height" Value="100" />
            <Setter Property="Fill" Value="LightGray" />
            <Setter Property="Stroke" Value="Gray" />
            <Setter Property="StrokeThickness" Value="2" />
            <Setter Property="Margin" Value="4" />
          </Style>
        </StackPanel.Resources>
        <Ellipse Fill="{Binding State, 
                        Converter={StaticResource colorConverter}, 
                        ConverterParameter=RED}" />
        <Ellipse Fill="{Binding State, 
                        Converter={StaticResource colorConverter}, 
                        ConverterParameter=YELLOW}" />
        <Ellipse Fill="{Binding State, 
                        Converter={StaticResource colorConverter}, 
                        ConverterParameter=GREEN}" />
      </StackPanel>
    </Border>
  </Grid>
</UserControl>
 
The first thing I do is create a ColorConverter object up in my control's resources. I then tell each Ellipse to use that converter and set the ConverterParameter to the appropriate light. And that does it for setting up the binding. Now when you launch the application, you get something that looks like this:

App Screenshot with Green Light

Now, whenever the State of the bound TrafficLight object changes, the binding will automatically update the display to the correct light. Let's add some buttons to the windows to illustrate this.
<Window x:Class="BindingConverterTutorial.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:my="clr-namespace:BindingConverterTutorial"
    Title="Window1" Height="512" Width="512">
  <Window.Resources>
    <my:TrafficLight State="Green" x:Key="myTrafficLight" />
  </Window.Resources>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="20" />
      <RowDefinition Height="350" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <my:TrafficLightControl
            Grid.Row="1"
            DataContext="{StaticResource myTrafficLight}" />
    <StackPanel Orientation="Horizontal" 
                HorizontalAlignment="Center"
                Grid.Row="2" 
                Height="30" >
      <Button Content="Green"
              Width="100"
              Margin="2"
              Click="ButtonClicked"
              Name="btnGreen" />
      <Button Content="Yellow"
              Width="100"
              Margin="2"
              Click="ButtonClicked"
              Name="btnYellow" />
      <Button Content="Red"
              Width="100"
              Margin="2"
              Click="ButtonClicked"
              Name="btnRed" />
    </StackPanel>
  </Grid>
</Window>
 
Here I've added three buttons that when clicked call the same event handler. Here's the code inside the event handler:
private void ButtonClicked(object sender, RoutedEventArgs e)
{
  TrafficLight tLight = (TrafficLight)this.FindResource("myTrafficLight");

  if (sender == btnGreen)
    tLight.State = TrafficLight.States.GREEN;
  else if (sender == btnYellow)
    tLight.State = TrafficLight.States.YELLOW;
  else if (sender == btnRed)
    tLight.State = TrafficLight.States.RED;
}
 
In this function I pull the TrafficLight object out of my window's resources. Then, depending on what button was pressed, I simply set the state of the light. The UI will now automatically update and fill the correct circle. That's all there is to it.

All Three Traffic Light States

I've found binding converters to be an indispensable resource when building WPF applications. It helps keep architectures clean by removing the need to contain UI logic and members within your data objects. It also greatly reduces the amount and complexity of code required to update the user interface. You can download all of the source code from here.
Source Files:

Comments

Popular posts from this blog

C# Snippet - Shuffling a Dictionary [Beginner]

Randomizing something can be a daunting task, especially with all the algorithms out there. However, sometimes you just need to shuffle things up, in a simple, yet effective manner. Today we are going to take a quick look at an easy and simple way to randomize a dictionary, which is most likely something that you may be using in a complex application. The tricky thing about ordering dictionaries is that...well they are not ordered to begin with. Typically they are a chaotic collection of key/value pairs. There is no first element or last element, just elements. This is why it is a little tricky to randomize them. Before we get started, we need to build a quick dictionary. For this tutorial, we will be doing an extremely simple string/int dictionary, but rest assured the steps we take can be used for any kind of dictionary you can come up with, no matter what object types you use. Dictionary < String , int > origin = new Dictionary < string , int >(); ...

C# WPF Printing Part 2 - Pagination [Intermediate]

About two weeks ago, we had a tutorial here at SOTC on the basics of printing in WPF . It covered the standard stuff, like popping the print dialog, and what you needed to do to print visuals (both created in XAML and on the fly). But really, that's barely scratching the surface - any decent printing system in pretty much any application needs to be able to do a lot more than that. So today, we are going to take one more baby step forward into the world of printing - we are going to take a look at pagination. The main class that we will need to do pagination is the DocumentPaginator . I mentioned this class very briefly in the previous tutorial, but only in the context of the printing methods on PrintDialog , PrintVisual (which we focused on last time) and PrintDocument (which we will be focusing on today). This PrintDocument function takes a DocumentPaginator to print - and this is why we need to create one. Unfortunately, making a DocumentPaginator is not as easy as...

C# WPF Tutorial - Implementing IScrollInfo [Advanced]

The ScrollViewer in WPF is pretty handy (and quite flexible) - especially when compared to what you had to work with in WinForms ( ScrollableControl ). 98% of the time, I can make the ScrollViewer do what I need it to for the given situation. Those other 2 percent, though, can get kind of hairy. Fortunately, WPF provides the IScrollInfo interface - which is what we will be talking about today. So what is IScrollInfo ? Well, it is a way to take over the logic behind scrolling, while still maintaining the look and feel of the standard ScrollViewer . Now, first off, why in the world would we want to do that? To answer that question, I'm going to take a an example from a tutorial that is over a year old now - Creating a Custom Panel Control . In that tutorial, we created our own custom WPF panel (that animated!). One of the issues with that panel though (and the WPF WrapPanel in general) is that you have to disable the horizontal scrollbar if you put the panel in a ScrollV...