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

Generic WPF/Silverlight Value Converter

Rate me:
Please Sign up or sign in to vote.
5.00/5 (9 votes)
24 Nov 2011CPOL9 min read 34.8K   304   20   6
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 bool 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 bool initially the ViewModel can save making changes in the ViewModel or the creation of a IValueConverter to convert from two values to another two values. The preferred way should always be to use a IValueConverter to convert the value (bool, or something else) to WPF. Creating a generic IValueConverter to convert a bool from the ViewModel to the enumeration (or some other value) needed by the View is actually quite easy. This can then be a IValueConverter 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.

Note

I have pretty much abandoned this since IValueConverter I think I have a much more elegant solution. Among the issues I have with this solution is that I have to declare this in the XAML bevore using it. I pretty much always now derive from my converters from the MarkupExtension so that I do not have to declare the converter before I use it. This converter is not compatible with the MarkupExtension since the TrueValue and FalseValue need to be set. I could overcome this and maybe I will investigate the possibility in the future. Ultimately it would probably even be a better solution. Here is the link to the article about the converter: http://www.codeproject.com/Articles/1017358/WPF-Converter-Helper-Class.  The way the TrueValue and FalseValue are passed into converters that use the helper class is through the ConverterParameter.

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 bool for some other purpose using a value converter (I believe it was a bool). 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:

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.

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:

C#
[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:

C#
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:

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

Image 1

The XAML for this is as follows:

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="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:

Image 2

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:

Image 3

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.

As I stated above, think that it should be possible to use the MarkupExtension class to make it so that this converter does not have to be declared before it is used.

Conclusion

This generic Boolean value converter is flexible enough to handle all bool 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 bool.

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

 
QuestionNice idea there, TypeConverters are pretty handy actually Pin
Sacha Barber4-Jan-12 0:00
Sacha Barber4-Jan-12 0:00 
AnswerRe: Nice idea there, TypeConverters are pretty handy actually Pin
Clifford Nelson10-Jan-12 13:58
Clifford Nelson10-Jan-12 13:58 
GeneralMy vote of 5 Pin
Rick Dean7-Dec-11 12:32
Rick Dean7-Dec-11 12:32 
AnswerRe: My vote of 5 Pin
Clifford Nelson9-Feb-12 11:17
Clifford Nelson9-Feb-12 11:17 
QuestionConverter article Pin
Rbucha29-Nov-11 7:45
Rbucha29-Nov-11 7:45 
AnswerRe: Converter article Pin
Clifford Nelson29-Nov-11 8:12
Clifford Nelson29-Nov-11 8:12 
I would definately go with a converter since it will be easier to encapsulate the logic. The converter would be bound to the name or enumeration of counties, depending on how you do it. For the image of the country flag, the converter would have a dictionary (built when the converter is initialized) which will associate the country name/enumeration with the path to the flag image.

For the countries, I would probably try to use an enumeration. The display name for the country would be the DescriptionAttribute associated with the specific enumeration (you can see other articles I have written on using the DescriptionAttribute. Then a generic type converter can built based on one I have created for the article that will provide the country name from the enumeration.

I am not sure how to distinguish a person in your design. A converter should be able to handle this also. If you are using a string for both country and person, it should be easy enough to use the dictionary to look up country flags to determine if the name is associated with a country. If it is not, then assume it is a person.Cool | :cool:

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.