This post provides a simple IValueConverter implementation that makes use of the framework type converters in order to convert between a large range of source / target types. This converter can be used both within bindings and in code-behind to give more concise property setters.
Introduction
One of the great features of the XAML language is that it is flexible, concise and expressive (yes, I know that XML can be a little verbose, but if you try to create a complex UI purely in code-behind, I think you will agree with my observations!). For example, you can set the fill of a rectangle by simply specifying the named color:
<Rectangle Fill="Green"/)
or … you can specify the RGB values directly:
<Rectangle Fill="#00FF00"/)
Looking at the above examples, you might be fooled into thinking that the Fill
property is of type Color
. People who are new to WPF often find that this is not the case the first time they try to bind a property of type Color
to the Fill
property (they would never set the Fill
property directly in code behind, because that would be a cardinal sin!). The Fill
property is actually of type Brush
, and the XAML parser is performing some cunning type conversions in order to make the above markup work.
The solution to this problem of binding a Color
to the Fill
property is to create a value converter:
public class ColorToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value is Color)
{
return new SolidColorBrush((Color)value);
}
return null;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new Exception("The method or operation is not implemented.");
}
}
Which can be used as follows:
<Rectangle Fill="{Binding Path=MyColorProperty,
Converter={StaticResource ColorToBrushConverter}} "/)
If you search Google for ColorToBrushConverter, you can see that there are a great many people who have implemented this simple little converter. But what happens if you want to bind to a string
representation of color
? or you want to bind to a stroke dash property or path geometry? Whilst value converters are simple to implement, it is a shame that you have to create so many of them!
A Universal Value Converter
Wouldn’t it be great to have a single value converter that has the same flexibility as the XAML parser? It is actually very simple to create such as converter (and after creating probably my 5th ColorToBrushConverter
, I have no idea why it took so long before I realized this!). The .NET Framework has had an API for conversion between different types via TypeConverters for a long time. They were used extensively in .NET technologies for databinding and designer support, and much more.
A value converter can obtain a suitable TypeConverter
for the target property, then perform the required conversion:
public class UniversalValueConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
TypeConverter converter = TypeDescriptor.GetConverter(targetType);
try
{
if (converter.CanConvertFrom(value.GetType()))
{
return converter.ConvertFrom(value);
}
else
{
return converter.ConvertFrom(value.ToString());
}
}
catch (Exception)
{
return value;
}
}
public object ConvertBack
(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Note that this value converter first tries to convert directly from the source to the target type, if this is not possible it then tries to convert via a string
representation (I am not sure whether it is more correct to obtain a TypeConverter
for the String
type, then use this rather that invoking ToString
on the value being converted, but the above works for me :) ).
You can see this converter in action below where a range of type conversions are demonstrated:
<TextBlock Text="Converting String to Brush ...."/>
<Rectangle Fill="{Binding ElementName=colorTextBox, Path=Text,
Converter={StaticResource UniversalValueConverter}}"/>
<TextBox x:Name="colorTextBox"
Text="Red"/>
<TextBlock Text="Converting String to Geometry ...."/>
<Path Data="{Binding ElementName=geometryText, Path=Text,
Converter={StaticResource UniversalValueConverter}}"/>
<TextBox x:Name="geometryText"
Text="M 10,20 C 10,2.5 40,35 40,17 H 28"/>
<TextBlock Text="Converting String to DoubleCollection (stroke dash) ...."/>
<Line StrokeDashArray="{Binding ElementName=dashText, Path=Text,
Converter={StaticResource UniversalValueConverter}}"/>
<TextBox x:Name="dashText"
Text="2 2 4 5"/>
For the first conversion, string
to brush, you can use named colors, and the hex notation in its range of abbreviated forms (#AF7, #AAFF77, #FFAAFF77 …). You can also use this converter to convert from string
to their corresponding enum
values, for example binding the string
“Collapsed
” to the Visibility
property.
Value Conversion in Code Behind
The above converter really is Swiss army knife for bindings, but what about code-behind? You are still constrained by the type requirements of the property being set:
rect1.Fill = new SolidColorBrush()
{
Color = Colors.Red
};
Value converters, whilst typically used in binding, can also be used directly in code-behind. The following extension method extends SetValue
method for setting dependency properties to make use of the above value converter:
public static void SetValueEx(this DependencyObject element,
DependencyProperty property, object value)
{
var conv = new UniversalValueConverter();
var convertedValue = conv.Convert(value, property.PropertyType,
null, CultureInfo.InvariantCulture);
element.SetValue(property, convertedValue);
}
Which provides a more flexible mechanism for setting property values:
rect1.SetValueEx(Rectangle.FillProperty, Colors.Red);
rect2.SetValueEx(Rectangle.FillProperty, "Blue");
rect3.SetValueEx(Rectangle.FillProperty, "#FFEE55");
rect4.SetValueEx(Rectangle.FillProperty, new SolidColorBrush(Colors.Orange));
… and Silverlight?
Unfortunately, Silverlight lacks the TypeDescriptor
class which is used to obtain TypeConveters
. I am guessing that type conversion within Silverlight is ‘baked-in’ to the XAML parser, which means that it is not possible to re-use this logic. :(
You can download the full source for this blog post from here.
Regards,
Colin E.