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
:
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
.
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:
[TypeConverter(typeof(MyClassTypeConverter))]
public class MyClass
{
}
public class MyClassTypeConverter : TypeConverter
{
public override object ConvertBack(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture,
object value,
Type destinationType)
{
}
}
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 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:
public class IfTrueValueConverter : IValueConverter
{
public object TrueValue { get; set; }
public object FalseValue { get; set; }
public object CompareTrue { get; set; }
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
.
The XAML for this is as follows:
<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:
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.
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.