Click here to Skip to main content
Click here to Skip to main content

Generic WPF/Silverlight Value Converter

, 24 Nov 2011
Rate this:
Please Sign up or sign in to vote.
A generic WPF/Silverlight value converter.

Introduction

One of the things I do not believe one should do is passing WPF/Silverlight enumerations (i.e., Visibility) directly from the ViewModel to the View. All bindings to a ViewModel that are two values should be defined as a Boolean in the ViewModel even if the View requires some other value to reduce coupling. One of the reasons is that at times properties in the ViewModel are used in ways that were not originally intended, and using Boolean initially the ViewModel can save making changes in the ViewModel or the creation of a value converter to convert from two values to another two values. The preferred way should always be to use a value converter to convert the value (Boolean, or something else) to WPF. Creating a generic value converter to convert a Boolean from the ViewModel to the enumeration (or some other value) needed by the View is actually quite easy. This can then be a value converter and can then be customized in XAML in the View or can be defined in a resource dictionary. Such a converter can also provide additional features to aid the programmer in finding bugs.

Background

Amazingly, I was on a Silverlight project for Microsoft where Visibility was being set in the ViewModel and I actually had to convert this Visibility to a Boolean for some other purpose using a value converter (I believe it was a Boolean). Due to administrative constraints in changes I could make, I could not make the changes to the ViewModel (I understand that they were probably going to fix this problem, but I left the project before the fix was implemented).

I probably started directly passing WPF enumerations from the ViewModel to the View, but I was pretty quick in creating custom value converters to do the work. It seemed wrong to have WPF enumerations in the ViewModel, and there was the IValueConverter interface that provides the customization needed. In creating this code, I was simultaneously cursing Microsoft for not providing a simple logic within XAML to allow for simple conversions or simple equations.

I did not like creating a custom converter for each conversion of a Boolean to some value needed in the View, and wrote a simple value converter that could be customized for the values associated with true and false in the View’s XAML:

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:

<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.

I have used this value converter extensively in my coding, and have considered writing an article on the idea for quite a while, but I knew I could do better. First of all, I could convert the string to the actual type so that string conversion would not be required except the first time, which should increase performance at a small cost during initialization. Second, I could do some error checking. The main purpose of the error checking would be to help the programmer find errors; there have been quite a few times I have spent too much time finding a problem in binding that was actually very simple, just that WPF/Silverlight does a very poor job of helping the developer with binding problems. The two objectives actually go hand in hand since converting a string to the required value and giving the developer feedback both require the conversion of the string to the type expected by WPF.

The important aspect of creating an implementation is converting between a string and the value, and vice versa.

I have often worked with enumerations in WPF, so was quite familiar converting strings to the enumerations and enumerations to string values. Also, I have used the above value converter mostly for enumerations, particularly Visibility, so was only initially thinking about doing the checking and conversion only for enumerations. When I started to implement my improved value converter, I did the enumeration part, and then thought that it should also be able to convert strings to other object types since WPF and Silverlight do this conversion. Figuring out how to do it was a bit trickier.

I initially attempted to use the System.Convert.ChangeType method, which did not work (did not surprise me). Research found the TypeConverter class. This class can be associated with the associated class it translates for by using an attribute:

[TypeConverter(typeof(MyClassTypeConverter))]
public class MyClass
{
    //Class implementation here
}
public class MyClassTypeConverter : TypeConverter
{
    public override object ConvertTo(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:

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

I originally had a different code for conversion of the enumeration since I could use the Enum.Parse method, but the TypeConverter works for both, and using only the TypeConverter eliminated code.

Implementation

The code for the converter is as follows:

public class IfTrueValueConverter : IValueConverter
{
    public object TrueValue { get; set; }
    public object FalseValue { get; set; }
    private bool _checkValues;
 
    public object Convert(object value, Type targetType, object parameter,
       System.Globalization.CultureInfo culture)
    {
      if (!_checkValues)
        Initialize(targetType);
      return (bool)value ? TrueValue : FalseValue;
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, 
      System.Globalization.CultureInfo culture)
    {
      if (!_checkValues)
        Initialize(value.GetType());
      return (value.ToString() == TrueValue.ToString());
    }
 
    private void Initialize(Type targetType)
    {
      _checkValues = true;
 
      if (TrueValue == null)
      {
        TrueValue = true;
        FalseValue = false;
      }
      else
      {
        TrueValue = FixValue(targetType, TrueValue);
        FalseValue = FixValue(targetType, FalseValue);
      }
    }
 
    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;
      }
    }
 
    [ConditionalAttribute("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 public methods are almost identical to the code above, except the creation of a private Initialize method to remove the checking out of the public methods. The _checkValues variable allows the initializing code to be skipped once initialization has occurred. The Initialize method checks if the TrueValue has been assigned (just like in the simpler implementation above), and makes it a Boolean if it has not. Next, the private static FixValue function is called for each of the TrueValue and FalseValue variables. The FixValue method first checks to make sure that conversion is required: if conversion is not required, just return the same value (if an attempt is made to convert twice, an exception will probably be raised). Otherwise, the code obtains the TypeConverter for the targetType, and does the conversion. If there is an exception (it is not possible to convert to a value of the right type), the code catches the exception and the original value is returned (probably this will not work, but WPF will let the code work). I have programmed the converter so that if the environment is in debug mode, then a message box will inform the developer of the reason for the exception. This is true because the method containing the MessageBox.Show method is decorated with “ConditionalAttribute("DEBUG")”. In this conditional method, the target type is checked for being an enumeration so that the message box content for an enumeration can include additional information about the acceptable enumeration values.

The Example

The example included shows this converter being used for a number of different properties on a TextBox. These properties are changed by checking CheckBoxes. One of the checkboxes controls the visibility, and this one needs to be checked to be able to see any of the other changes. Among the properties that are bound are double (FontSize), Brush (BorderBrush), Visibility, reversed Boolean (IsReadOnly), and Thickness (BorderThickness). This should be a pretty good variety of properties and types to show the flexibility of the value converter.

The XAML for this is as follows:

<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="250" Width="450">
  <Window.Resources>
    <local:IfTrueValueConverter x:Key="VisibilityConverter" 
      TrueValue="Visible" FalseValue="Hidden"/>
    <local:IfTrueValueConverter x:Key="BorderColorConverter" 
      TrueValue="Red" FalseValue="Blue"/>
    <local:IfTrueValueConverter x:Key="ReverseBoolConverter" 
      TrueValue="false" FalseValue="true"/>
    <local:IfTrueValueConverter x:Key="BorderThicknessConverter" 
      TrueValue="3,4,5,6" FalseValue="1,2,3,4"/>
    <local:IfTrueValueConverter x:Key="FontSizeConverter" 
      TrueValue="10" FalseValue="12.5"/>
  </Window.Resources>
  <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
    <CheckBox Content="checked for visible, unchecked for invisible"
                IsChecked="{Binding IsVisible,FallbackValue=true}"/>
    <CheckBox Content="checked for Red border color, unchecked for Blue border color"
                IsChecked="{Binding BorderColor,FallbackValue=true}"/>
    <CheckBox Content="checked to enable editing (IsReadOnly = false)"
                IsChecked="{Binding CanEdit,FallbackValue=true}"/>
    <CheckBox Content="checked for thick border thickness, unchecked for thinner"
                IsChecked="{Binding ThinBorderThickness,FallbackValue=true}"/>
    <CheckBox Content="checked for font size 10, unchecked for font size 12.5"
                IsChecked="{Binding FontSize,FallbackValue=true}"/>
 
    <TextBlock Text="TestControl:" Margin="0,5,0,0" Foreground="DarkBlue"/>
    <TextBox Name="TestControl" Text="Test Message" Margin="5" Height="30"
             Visibility="{Binding IsVisible,
               Converter={StaticResource VisibilityConverter}}"
             BorderBrush="{Binding BorderColor,
               Converter={StaticResource BorderColorConverter}}"
             BorderThickness="{Binding ThinBorderThickness,
               Converter={StaticResource BorderThicknessConverter}}"
             FontSize="{Binding FontSize,
              Converter={StaticResource FontSizeConverter}}"
             IsReadOnly="{Binding CanEdit,Converter={StaticResource 
               ReverseBoolConverter}}"/>
  </StackPanel>
</Window>

As can be seen, using the converter is just like using the simpler one I showed above.

You will have to modify the XAML to show the debugging assistance that this code provides. All that is required is that either 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:

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:

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.

Possible Improvements and Options

One of the ideas I have had is to extend the converter to be three-state where the third state is null. Personally, I have not seen any need for this third value in my code, but I can see cases where a third value is desirable.

Another idea is to add properties that allow the value to be compared to something other than true and false. This would be pretty straightforward, and could be very useful if binding to properties of a control defined in the XAML.

An extension of this would be support for more than two or three values. This could be done by defining all the items in a delimited list in the XAML that is entered as a property in the converter. The converter would then parse the list to get the conversions.

Conclusion

This generic Boolean value converter is flexible enough to handle all Boolean conversions required by a View. It can also be used in certain circumstances to do other binding where the value being bound to is a Boolean.

License

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

About the Author

Clifford Nelson
Software Developer (Senior) ETeam/Delloitte
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 three years has been working primarily as a senior WPF/C# and Silverlight/C# developer. Currently working as WPF developer with NBC Universal in Universal City, CA

Comments and Discussions

 
QuestionNice idea there, TypeConverters are pretty handy actually PinmvpSacha Barber4-Jan-12 0:00 
AnswerRe: Nice idea there, TypeConverters are pretty handy actually PinmemberClifford Nelson10-Jan-12 13:58 
GeneralMy vote of 5 PinmemberRick Dean7-Dec-11 12:32 
Clifford--I really thought this was a great article. Your converter should be in everyone's WPF toolbox. Thanks so much.
AnswerRe: My vote of 5 PinmemberClifford Nelson9-Feb-12 11:17 
QuestionConverter article PinmemberRbucha29-Nov-11 7:45 
AnswerRe: Converter article PinmemberClifford Nelson29-Nov-11 8:12 

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

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

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 24 Nov 2011
Article Copyright 2011 by Clifford Nelson
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid