WPF Simplified Part 13: Value Converters
February 22, 2010 3 Comments
In WPF, when you data bind an element’s property to a data source, if the source property type and target property type don’t match you’ll need a value converter to hook them up correctly.
Consider the following data bound TextBlock,
<TextBlock x:Name="MyText" Text="{Binding Name}" Foreground="{Binding DisplayColor}" />
and the code behind,
this.MyText.DataContext = new { Name = "John", DisplayColor = "BlackSmoke" };
If you try out the code and XAML above you’ll see the following error in VisualStudio’s ‘Output’ window,
System.Windows.Data Error: 6 : ‘TargetDefaultValueConverter’ converter failed to convert value ‘BlackSmoke’ (type ‘String’); fallback value will be used, if available. BindingExpression:Path=DisplayColor; DataItem=’<>f__AnonymousType0`2′ (HashCode=-850673272); target element is ‘TextBlock’ (Name=’MyText’); target property is ‘Foreground’ (type ‘Brush’) FormatException:’System.FormatException: Token is not valid.
The error is that TextBlock’s Foreground accepts a Brush type, but we bound the Foreground to a String type, hence the binding failed. What we need is a value converter that will convert a String type into a Brush type.
[ValueConversion(typeof(string), typeof(Brush))] public class StringToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is string) { string source = value as string; if (source == "BlackSmoke") return new SolidColorBrush(Colors.DimGray); } // Default color return Colors.Black; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { // To keep it simple, no need to convert back return null; } }
As you can see, we’ve hand coded how a String will convert to a Brush type for our application – when the string is “BlackSmoke” we’d like the Brush to be “DimGray”. Applying this converter in XAML is simple enough,
<Grid> <Grid.Resources> <local:StringToBrushConverter x:Key="StringToBrushConverter" /> </Grid.Resources> <TextBlock x:Name="MyText" Text="{Binding Name}"
Foreground="{Binding DisplayColor, Converter={StaticResource StringToBrushConverter}}" /> </Grid>
Thus, even though the TextBlock’s Foreground is still bound to a String type, our converter will provide the required type (Brush) to the Foreground dependency property.
WPF also has some built-in value converters, in fact, it has a built in String to Brush converter! To see this, try the following XAML,
<TextBlock x:Name="MyText" Text="{Binding Name}" Foreground="{Binding DisplayColor}" />
with the following code behind,
this.MyText.DataContext = new { Name = "John", DisplayColor = "Red" };
The text displays in red because of WPF’s built in String to Brush converter – it recognized the string ‘Red’ and converted it to the corresponding Brush – Colors.Red. In fact the built-in converter will recognize any of the colors in System.Windows.Media.Colors as strings, and will convert them to their corresponding Brushes. The code probably does the equivalent of the following,
if (source == "Red") return new SolidColorBrush(Colors.Red); if (source == "Yellow") return new SolidColorBrush(Colors.Yellow);
// etc.
Instantiating the converter in XAML as a resource can be cumbersome, so there is another way to use value converters. By deriving from MarkupExtension, you can use the converter directly in XAML, without using StaticResource.
[ValueConversion(typeof(string), typeof(Brush))] public class StringToBrushConverter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider) { return this; } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is string) { string source = value as string; if (source == "BlackSmoke") return new SolidColorBrush(Colors.DimGray); } // Default color return Colors.Black; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { // To keep it simple, no need to convert back return null; } }
and now you can use the converter like this,
<TextBlock x:Name="MyText" Text="{Binding Name}"
Foreground="{Binding DisplayColor, Converter={local:StringToBrushConverter}}" />