Click here to Skip to main content
15,867,330 members
Articles / Desktop Programming / WPF

Generic WPF-Silverlight implementation for IValueConverter

Rate me:
Please Sign up or sign in to vote.
5.00/5 (11 votes)
18 Jun 2012CPOL6 min read 30.5K   226   17   13
A generic implementation for IValueConverter.

Introduction

I originally created a simpler form of this value converter for simple conversions of bool properties to custom values, in particular converting Boolean to Visibility. This was published in CodeProject as “Generic WPF/Silverlight IValueConverter” (http://www.codeproject.com/Articles/289495/Generic-WPF-Silverlight-Value-Converter). I got tired of recreating the wheel for slight changes in the values, and cluttering up my application with numerous value converters. Recently I was working on a new project, and really did not want to add a bool property for each special state. This would almost be as bad as adding a special property just for Visibility. I got the idea of adding a third customizable property to my converter. So now I have a much more flexible value converter.

Background

I originally created the following very basic converter that allowed me to bind to a bool property and return something else that was defined by setting two properties on the IValueConverter:

C#
public class IfTrueValueConverter : IValueConverter
{
    public object TrueValue { get; set; }
    public object FalseValue { get; set; }
 
    public object Convert(object value, Type targetType, object parameter, 
      System.Globalization.CultureInfo culture)
    {
      if (TrueValue == null)
      {
        TrueValue = true;
        FalseValue = false;
      }
      return (bool)value ? TrueValue : FalseValue;
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, 
      System.Globalization.CultureInfo culture)
    {
      if (TrueValue == null)
      {
        TrueValue = true;
        FalseValue = false;
      }
      return (value.ToString() == TrueValue.ToString());
    }
}

The XAML to use this converter is very similar to using a simple converter except that true and false values are defined as part of the defining of the converter in the resources:

XML
<Window x:Class="GenericValueConverter.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:GenericValueConverter"
  Title="IfTrueValueConverter example" Height="350" Width="525">
  <Window.Resources>
    <local:IfTrueValueConverter x:Key="VisibilityConverter" 
      TrueValue="Visible" FalseValue="Hidden"/>
  </Window.Resources>
  <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
    <TextBox Name="TestControl" Text="Test Message" Margin="5"
       Visibility="{Binding IsVisible,
            Converter={StaticResource VisibilityConverter}}"/>
  </StackPanel>
</Window>

As can be seen, first we need to define the value converter in the Window.Resources element of the XAML. This is just defining any value converter for use except there are two additional arguments: TrueValue and FalseValue. All that is needed is to put in the string value for the enumeration desired for when the ViewModel’s value is true and false. The conversion capability of WPF is such that it can convert these string values into the enumeration, so the converter works. The converter can also be used to set colors, so string values of “Red” and “Black” can be assigned to TrueValue and FalseValue, and then the converter can be used to set the Foreground Brush.

Using the converter is now just like using any converter as can be seen in the XAML for the TextBox.

Type Conversion

When I was working on improving this converter, I figured I could improve the performance by taking the TrueValue and FalseValue properties, and converting them to the correct types the first time, thus saving the time required for the automatic conversion during the value converter conversion. For this I used the TypeConverter class. This class can be associated with the associated class it translates for by using an attribute:

C#
[TypeConverter(typeof(MyClassTypeConverter))]
public class MyClass
{
    //Class implementation here
}
public class MyClassTypeConverter : TypeConverter
{
    public override object ConvertBack(ITypeDescriptorContext context,
        System.Globalization.CultureInfo culture,
        object value,
        Type destinationType)
    {
      //Conversion code here, returning object;
    }
}

Microsoft has type converters for many of the standard classes. All that is required is to use the static TypeDescriptor.GetConverter method, and then the ConvertFrom method of the returned class:

C#
TypeConverter converter = TypeDescriptor.GetConverter(targetType);
return converter.ConvertFrom(value);

I used this conversion on both the TrueValue and FalseValue properties, which where both object. This meant that a simple cast is all that is required to use the properties. The targetType argument in the Convert method is used to determine what Type to convert the properties if the Convert method is the first method to execute (the ConvertBack method will also initialize the values, but I think that this is probably redundant).

More Properties to Increase Flexibility

To meet the immediate need for a converter I added what I initially called Compare (later changed to CompareTrue). The idea was to compare the property to the value argument passed in the Convert method with this value, and if they are the same, then the TrueValue is returned by the value converter Convert method. Now since I will know the type of the value argument by checking its type, I can use the TypeConverter on this property also.

Having just this CompareTrue property worked fine as long as I did not worry about the ConvertBack method. For most everything I have used this converter for, I have never used the ConvertBack method, but I should consider it. Therefore I added a CompareFalse property.

Unfortunately I then had to consider the fact that may be dealing with types where wanted to compare null values. This added a fair amount of complexity since I wanted a default value of true and false if the type of the value argument in the Convert method was bool, but there are valid cases for null for most types. During initialization the CompareTrue and CompareFalse properties are maintained at null unless they type for comparison if Boolean, in which case it is changed to the bool value of true and false, respectively. If the CompareTrue and CompareFalse properties are not null then the values are fixed using the type’s TypeConverter.

I added one last extra property because I had a specific use for it as a default and that was a null value. I have to admit that it added probably more complexity than worth it for a general converter, but all value types can be made Nullable, and objects can be null.

Implementation

The code for the converter is as follows:

C#
public class IfTrueValueConverter : IValueConverter
{
    // Default for comparison is true and false, otherwise need to have values set in 
    // XAML definition for value converter
    public object TrueValue { get; set; }
    public object FalseValue { get; set; }
    public object CompareTrue { get; set; }
    //Note: CompareFalse is only significant for ConvertBack
    public object CompareFalse { get; set; }
    public object NullValue { get; set; }
    private bool _checkValues;

    public object Convert(object value, Type targetType, object parameter, 
        System.Globalization.CultureInfo culture)
    {
      if (!_checkValues && value != null)
        Initialize(targetType, value.GetType());
      if (value == null) return 
        CompareTrue == null ? TrueValue:
           (FalseValue == null ? FalseValue : NullValue);
      return CompareTrue.Equals(value) ? TrueValue : 
        (CompareFalse == null ? FalseValue :
          (CompareFalse.Equals(value) ? FalseValue : NullValue));
    }

    public object ConvertBack(object value, Type targetType, object parameter, 
        System.Globalization.CultureInfo culture)
    {
      if (!_checkValues && value != null)
        Initialize(value.GetType(), targetType);
      return TrueValue.Equals(value) ? CompareTrue : CompareFalse;
    }

    private void Initialize(Type targetType, Type compareType)
    {
      _checkValues = true;

      TrueValue = (TrueValue != null) ? FixValue(targetType, TrueValue) : true;
      FalseValue = (FalseValue != null) ? FixValue(targetType, FalseValue) : false;
      NullValue = (NullValue != null) ? NullValue = FixValue(targetType, NullValue) 
        : null;
      CompareTrue = (CompareTrue != null) ? FixValue(compareType, CompareTrue) : true;
      CompareFalse = (CompareFalse != null) ? FixValue(compareType, CompareFalse) :
        ((compareType.FullName == (typeof(bool)).FullName) ? (object)false : null);
    }

    private static object FixValue(Type targetType, object value)
    {
      if (value.GetType() == targetType)
        return value;
      try
      {
        TypeConverter converter = TypeDescriptor.GetConverter(targetType);
        return converter.ConvertFrom(value);
      }
      catch
      {
        DisplayIssue(targetType, value);
        return value;
      }
    }

    [Conditional("DEBUG")]
    private static void DisplayIssue(Type targetType, object invalidValue)
    {
      if (targetType.IsEnum)
      {
        var enumNames = string.Join(", ", Enum.GetNames(targetType));
        MessageBox.Show(string.Format(
          "Enumeration value '{0}' not recognized for enumeration type '{1}'. Valid values are {2}.",
          invalidValue, targetType, enumNames));
      }
      else
        MessageBox.Show(string.Format(
          "The value '{0}' not recognized for target type '{1}'.",
          invalidValue, targetType));
    }

The Example

The example shows the converter being used to change the Foreground and Background of some controls. The following shows the changes with each selection of the ComboBox.

Image 1

Image 2

Image 3

Image 4

The XAML for this is as follows:

XML
<Window x:Class="ValueConverterExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:local="clr-namespace:ValueConverterExample"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ValueConverter Example"
        Height="150"
        Width="300">
  <Window.DataContext>
    <local:ViewModel />
  </Window.DataContext>
  <Window.Resources>
    <local:IfTrueValueConverter x:Key="converterOne"
                                TrueValue="DarkGreen"
                                FalseValue="DarkGoldenrod"
                                NullValue="DarkRed"
                                CompareTrue="Green Color" />
    <local:IfTrueValueConverter x:Key="converterTwo"
                                TrueValue="LightGreen"
                                FalseValue="LightYellow"
                                NullValue="Pink"
                                CompareTrue="Green Color"
                                CompareFalse="Yellow Color" />
  </Window.Resources>
  <StackPanel HorizontalAlignment="Center"
              VerticalAlignment="Center"
              Orientation="Horizontal"
              Background="{Binding SelectedItem,Converter={StaticResource converterTwo}}">
    <Label Content="Select a value"
           Foreground="{Binding SelectedItem,
            Converter={StaticResource converterOne}}" />
    <ComboBox Name="comboBox"
              Width="100"
              SelectedItem="{Binding SelectedItem}"
              ItemsSource="{Binding ItemsSource}"
              Background="Transparent" />
  </StackPanel>
</Window>

I have shown two cases, one where the CompareFalse is not set and the other where the CompareFalse is set. There is an obvious argument for a property that will be used in the Convert method when the value is neither equal to CompareTrue or CompareFalse, but I have not had a need yet, so did not implement such a property. Also, that would have raised some issues about how to handle the ConvertBack return value.

Built In Debugging Assistance

You will have to modify the XAML to show the debugging assistance that this code provides. All that is required is that one of the properties like TrueValue or FalseValue be assigned an invalid value when the converter is defined in the XAML. If TrueValue for Visibility is changed to something like “illegal”, then the following MessageBox would appear:

http://www.codeproject.com/KB/WPF/GenericValueConverter/image002.png

This information should provide a lot of help in fixing binding issues for enumerations. For non-enumerations, the MessageBox is slightly simpler without the list of valid enumeration values:

http://www.codeproject.com/KB/WPF/GenericValueConverter/image003.png

One of the nice things is that the dialog is only displayed the first time the converter is run.

There are other ways to provide feedback on translation issues, including writing to the Output window, but I prefer displaying a message to the developer.

Conclusion

This IValueConverter can deal with simple bool comparisons, but this is more than adequate for most applications. Note many places you would use this converters can be handled with triggers in WPF; Silverlight does not currently support triggers. However, I have found using this converter and then converting to triggers makes things easier because I can use the debugger with converters and I cannot with triggers.

One issue I have found with debugging using this converter is that it is used for much more than a simple converter and sometimes debugging can be difficult. I have used the ConverterParameter to help me with debugging.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Clifford Nelson Consulting
United States United States
Has been working as a C# developer on contract for the last several years, including 3 years at Microsoft. Previously worked with Visual Basic and Microsoft Access VBA, and have developed code for Word, Excel and Outlook. Started working with WPF in 2007 when part of the Microsoft WPF team. For the last eight years has been working primarily as a senior WPF/C# and Silverlight/C# developer. Currently working as WPF developer with BioNano Genomics in San Diego, CA redesigning their UI for their camera system. he can be reached at qck1@hotmail.com.

Comments and Discussions

 
SuggestionNext step Pin
Yury Goltsman19-Jun-12 7:10
Yury Goltsman19-Jun-12 7:10 
AnswerRe: Next step Pin
Clifford Nelson19-Jun-12 8:23
Clifford Nelson19-Jun-12 8:23 
GeneralRe: Next step Pin
Yury Goltsman19-Jun-12 8:36
Yury Goltsman19-Jun-12 8:36 
AnswerRe: Next step Pin
Clifford Nelson19-Jun-12 10:23
Clifford Nelson19-Jun-12 10:23 
GeneralRe: Next step Pin
Yury Goltsman19-Jun-12 20:49
Yury Goltsman19-Jun-12 20:49 
GeneralRe: Next step Pin
jogibear998831-Aug-13 2:46
jogibear998831-Aug-13 2:46 
QuestionGreat article. Question though. Pin
db7uk18-Jun-12 23:29
db7uk18-Jun-12 23:29 
AnswerRe: Great article. Question though. Pin
Clifford Nelson19-Jun-12 6:29
Clifford Nelson19-Jun-12 6:29 
I have had it pop up during debuggin with no problem. It should only appear in debug mode, so should not be an issue. Should only see this happen due to severe design issues like mispelling a property name such as "Hidden".

Thanks for the 5.
GeneralRe: Great article. Question though. Pin
db7uk19-Jun-12 9:30
db7uk19-Jun-12 9:30 
QuestionNice one Pin
Sacha Barber18-Jun-12 23:09
Sacha Barber18-Jun-12 23:09 
AnswerRe: Nice one Pin
Clifford Nelson19-Jun-12 6:30
Clifford Nelson19-Jun-12 6:30 
Questionnice article Pin
Member 913890418-Jun-12 18:04
Member 913890418-Jun-12 18:04 
AnswerRe: nice article Pin
Clifford Nelson19-Jun-12 6:33
Clifford Nelson19-Jun-12 6:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.