Converters In WPF

Introduction

In WPF when our application is compliant with the MVVM pattern, we need to bind graphical items in the views to the corresponding properties in the view model.

The classical case, is when we bind a string to the value of a textbox/textblock … or a list to a datagrid or a combobox …

But sometimes, we need to bind two things that have not the same types, for example the color of the elements changes according to a specific enumeration, or when we want to bind a Boolean to the visibility and there is a lot of other cases.

In such cases, we need to do a couple of stuff to make things works, here we will need the converters.

Why using converters?

You can say, why do we need to use converters to do such things? For example, if we want to bind a visibility to a boolean, we can create a property having the type visibility in the viewModel, then we change it according to the Boolean, and it’s done.

The answer is YES you can do that, and it will certainly work. BUT there is a BUT!

The principle of MVVM is the separation of concerns, so a viewModel does not have to handle graphical items like Visibility, colors, etc. it has only to manage the logical stuffs and the processing. So the converters comes to avoid having antipatterns in your application.

Another point, for code maintenance, this allows to centralize the conversion processing, if for example you want to change the way of conversion, you need to do it only in the converter instead of going through all your code.

And for Reusability, if you centralize your conversions in one class, no need to redefine the code to reuse the same conversion in another view.

Audience

This Article is for intermediate developers who are familiar with the MVVM pattern.

Prerequisite

  • Visual Studio 2022
  • Netcore 60 (netcore 6.0 only to run the attached sample, but the code can work under donet 4.X framework also)

Goals

The goal is to have a sample application with three converters,

  • The first one shows or hides a label according to a Boolean flag bound to a radio button.
  • The second one changes the color of the background according to an Enum
  • The third one changes the size of a label according to an enum

Let's Code,

The converter is a separate class that should implement the interface IValueConverter.

Then you need to implement the body of two methods :

Convert

This method is the one responsible for changing the bound value of the source, to the value that the graphical element requires.

It accepts as parameters:

  • The value: which is the source (in our sample the value that comes from viewModel) passed automatically by the binding.
  • The targetType: which is the type of the property of the graphical element that calls the converters. It’s also passed automatically according to the call of the converter in the XAML
  • Parameter: an optional parameter that might be needed during the conversion.
  • Culture: it’s the culture of the calling application.

ConvertBack

In case of the inverse operation of conversion is needed, then you need to define the convertBack method.

Now let's see concretely our converters.

C# Part

The first thing, we will define the enumeration that will be used in the conversion.

Enumerations:

public enum Safety {
    Safe,
    Risky,
    Dangerous
}
public enum TextSize {
    Small,
    Medium,
    Big
}

Converter 1

Our first converter should convert the boolean to visibility, the definition and the implementation of the class is as follows:

public class BooleanToVisibilityConverter: IValueConverter {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
        try {
            var boolValue = (bool) value;
            if (boolValue) return Visibility.Visible;
            else return Visibility.Collapsed;
        } catch (Exception ex) {
            throw new Exception(ex.Message);
        }
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
        try {
            if (((Visibility) value).Equals(Visibility.Collapsed)) return false;
            else return true;
        } catch (Exception ex) {
            throw new Exception(ex.Message);
        }
    }
}

Converter 2

Our second converter should convert an enumeration to a size, the definition and the implementation of the class is as follows:

public class EnumToFontSizeConverter: IValueConverter {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
        try {
            if (((TextSize) value).Equals(TextSize.Small)) return 12;
            else if (((TextSize) value).Equals(TextSize.Medium)) return 24;
            else if (((TextSize) value).Equals(TextSize.Big)) return 48;
            return 12;
        } catch (Exception ex) {
            throw new Exception(ex.Message);
        }
    }
    /// <summary>
    /// No need to convert back in this sample
    /// </summary>
    /// <param name="value"></param>
    /// <param name="targetType"></param>
    /// <param name="parameter"></param>
    /// <param name="culture"></param>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
        throw new NotImplementedException();
    }
}

Converter 3

Our third converter should convert an enumeration to a color, the definition and the implementation of the class is as follows:

public class SafetyToColorConverter: IValueConverter {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
        try {
            if (((Safety) value).Equals(Safety.Dangerous)) return "Red";
            else if (((Safety) value).Equals(Safety.Risky)) return "Orange";
            else if (((Safety) value).Equals(Safety.Safe)) return "Green";
            return "White";
        } catch (Exception ex) {
            throw new Exception(ex.Message);
        }
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
        throw new NotImplementedException();
    }
}

ViewModel

And before starting the view, we need off course to define the view Model to have everything ready for the binding

public class MainViewModel: INotifyPropertyChanged {
    #region Constructor
    public MainViewModel() {
        SelectedSize = TextSize.Small;
        SelectedSafety = Safety.Safe;
    }
    #endregion
    #region Safety Combo
    public List < Safety > SafetyList {
        get {
            return Enum.GetValues(typeof(Safety)).Cast < Safety > ().ToList();
        }
    }
    private Safety _selectedSafety;
    public Safety SelectedSafety {
        get {
            return _selectedSafety;
        }
        set {
            _selectedSafety = value;
            NotifyPropertyChanged("SelectedSafety");
        }
    }
    #endregion
    #region Text Content
    private string _textContent;
    public string TextContent {
        get {
            return _textContent;
        }
        set {
            _textContent = value;
            NotifyPropertyChanged("TextContent");
        }
    }
    private void UpdateText() {
        switch (SelectedSize) {
            case TextSize.Small:
                TextContent = "I'm Small";
                break;
            case TextSize.Medium:
                TextContent = "I'm Medium";
                break;
            case TextSize.Big:
                TextContent = "I'm Big";
                break;
            default:
                break;
        }
    }
    #endregion
    #region Text Size
    public List < TextSize > TextSizeList {
        get {
            return Enum.GetValues(typeof(TextSize)).Cast < TextSize > ().ToList();
        }
    }
    private TextSize _selectedTextSize;
    public TextSize SelectedSize {
        get {
            return _selectedTextSize;
        }
        set {
            _selectedTextSize = value;
            NotifyPropertyChanged("SelectedSize");
            UpdateText();
        }
    }
    #endregion
    #region Visibility
    private bool _isVisibleChecked;
    public bool IsVisibleChecked {
        get {
            return _isVisibleChecked;
        }
        set {
            _isVisibleChecked = value;
            NotifyPropertyChanged("IsVisibleChecked");
        }
    }
    #endregion
    #region Property Changed
    public event PropertyChangedEventHandler ? PropertyChanged;
    public void NotifyPropertyChanged([CallerMemberName] string propertyName = "") {
        if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    #endregion
}

XAML Part

Now you've defined your converters, you need to call them in the Xaml.

The first thing, you need to call the namespace of the converters and define them as resources of your View

<Window x:Class="ConvertersSample.MainApplication.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:converters="clr-namespace:ConvertersSample.MainApplication.Converters" mc:Ignorable="d" Title="MainWindow" Height="450" Width="400">
  <Window.Resources>
    <converters:SafetyToColorConverter x:Key="safetyConverter"></converters:SafetyToColorConverter>
    <converters:BooleanToVisibilityConverter x:Key="boolVisConverter"></converters:BooleanToVisibilityConverter>
    <converters:EnumToFontSizeConverter x:Key="EnumSizeConverter"></converters:EnumToFontSizeConverter>
  </Window.Resources>

Then for every component, call your converter :

Converter 1

 <GroupBox Grid.Row="0" Header="Visibility Converter" Height="100">
   <StackPanel Margin="10">
     <StackPanel Orientation="Horizontal">
       <RadioButton Content="Show the Text" GroupName="VisibilityRadio" IsChecked="{Binding IsVisibleChecked}"></RadioButton>
       <RadioButton Content="Hide the Text" GroupName="VisibilityRadio" Margin="20 0 0 0"></RadioButton>
     </StackPanel>
     <TextBlock Text="I'm Visible" Margin="10" FontSize="18" Visibility="{Binding IsVisibleChecked, Converter={StaticResource boolVisConverter},Mode=TwoWay,NotifyOnSourceUpdated=True,UpdateSourceTrigger=PropertyChanged}"></TextBlock>
   </StackPanel>
 </GroupBox>

Converter 2

 <GroupBox Grid.Row="1" Header="Text Size Converter" Height="150">
   <StackPanel Margin="10">
     <ComboBox ItemsSource="{Binding TextSizeList}" SelectedItem="{Binding SelectedSize}"></ComboBox>
     <TextBlock Text="{Binding Path=TextContent,Mode=TwoWay}" FontSize="{Binding Path=SelectedSize,Converter={StaticResource EnumSizeConverter},Mode=TwoWay}" Margin="10"></TextBlock>
   </StackPanel>
 </GroupBox>

Converter 3

 <GroupBox Grid.Row="2" Header="Color Converter" Height="100">
   <StackPanel Background="{Binding Path=SelectedSafety, Converter={StaticResource safetyConverter},Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" Margin="10">
     <ComboBox ItemsSource="{Binding SafetyList}" SelectedItem="{Binding SelectedSafety}" Margin="10"></ComboBox>
   </StackPanel>
 </GroupBox>

Conclusion

Finally, we can say that the converters are a very good concept to have a clean code and respect the Single Responsibilty principle by avoiding the view Model to handle graphical Items.

I hope that this article helps for a better understanding. If you have any remark or question, please feel free to comment or reach me directly.

Happy Coding!