Introduction
Binding infrastructure provided by WPF is a great step forward compared with the available in WinForms. Thanks to the expressiveness provided by Xaml and, specifically
Markup Extensions, now we have flexibility choosing the binding Mode and Source.
When a converter is needed, however, the expressiveness of Markup Extensions reach their limit and all the bloat code appears. In order to bind a color property to the background of a Border element (a Brush) you need to:
1. Write a class that implements IValueConverter (2 methods to implement):
public class BushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new SolidColorBrush((Color)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
2. Probably add the namespace at the top of your Xaml file:
<Window
(...)
xmlns:w="clr-namespace:Bugs.Windows" />
3. Add an instance of the converter to the Windows Resources:
<Window.Resources>
<w:BushConverter x:Key="brushConverter"/>
</Window.Resources>
4. Actually use the converter in your binding using StaticResource:
<Border .... Background="{Binding SelectedColor, ElementName=cp, Converter={StaticResource brushConverter}}" />
C'mon Redmond, you can do better! What is this?
Java? (BTW Stevey, pleeease, it's time to reconsider your opinion on C# 3.0).
ConverterFactory
Our small step forward in this problem is using an small set of general purpose converters and relying on lambdas to do the actual conversion. This way you need to create a new converter object, not a new class, every time you need a converter:
1. Create a new converter object using ConverterFactory and store then in a public static readonly field in your own converter repository class.
public static class MyConverters
{
(...)
public static readonly IValueConverter ColorBrushConverter = ConverterFactory.New(
(Color color) => new SolidColorBrush(color));
(...)
}
2. Actually use the converter in your binding using x:Static:
<Border .... Background="{Binding SelectedColor, ElementName=cp, Converter={x:Static w:MyConverters.ColorBrushConverter}}" />
Notice how, by explicitly specifying Color as the first parameter in the lambda, we are helping type inference algorithm to succeed. This way we save ourselves from writing the type parameters explicitly: ConverterFactory.New<Color,SolidColorBrush>(...)
Let's take a look at the actual method provided by ConverterFactory:
public static class ConverterFactory
{
public static Converter<S, T> New<S, T>(Func<S, T> convert)
public static DualConvertet<S1, S2, T> New<S1, S2, T>(Func<S1, S2, T> convert)
public static MultiConverter<S, T> NewMulti<S, T>(Func<S[], T> convert)
public static Converter<S, T> New<S, T>(Func<S, T> convert, Func<T, S> convertBack)
public static ConverterValidator<S, T> New<S, T>(Func<S, T> convert, Func<T, S> convertBack, Func<S, string> validator)
}
public class Converter<S, T> : IValueConverter
{
internal Func<S, T> convert;
internal Func<T, S> convertBack;
(...)
}
public class DualConvertet<S1, S2, T> : IMultiValueConverter
{
internal Func<S1, S2, T> convert;
(...)
}
public class MultiConverter<S, T> : IMultiValueConverter
{
internal Func<S[], T> convert;
(...)
}
public class ConverterValidator<S, T> : ValidationRule, IValueConverter
{
internal Func<S, T> convert;
internal Func<T, S> convertBack;
internal Func<S, string> validator;
(...)
}
This technique removes a lot of the bloat code needed to implement the converter, and also centralizes all your converters in the same repository class, making it easier to maintain and reuse them.
There are more
breave strategies to improve Converter integration in Xaml. Fikrim Var Netleştirelim comes with the great idea of
writing the lambdas straight in Xaml. The implementation looks a bit experimental though, it needs a whole parser to do it, and relies on your writing lambdas in string literal. Not IntelliSense. No type checking.
Piping Converters
Based on
Josh Smith Tutorial, we also created the PipeExtension to enable concatenation of Converters.
PipeExtension is a Markup Extension and a Converter at the same time.
public class PipeExtension : MarkupExtension, IValueConverter
{
public IValueConverter First { get; set; }
public IValueConverter Second { get; set; }
(...)
}
- As a Markup Extension it has two parameters, First and Second, both of types IValueConverter. It also returns itself when ProvideValue is called.
- As a Converter it uses the First converter to get an intermediate value, and uses the Second converter to get the final one. It also works backwards.
Finally, since it only needs the converters to be IValueConverters, it works on both the new ConverterFactory and the old fashioned ones.
Let's see an example, imagine you already have a converter to make colors darker:
public static readonly IValueConverter DarkColor = ConverterFactory.New(
(Color c) => Color.FromArgb(c.A, (byte)(c.R / 2), (byte)(c.G / 2), (byte)(c.B / 2)));
Now you want to use it to bind to the background property of a border, that still is a Bush. Instead of writing a new converter you can 'pipe' the two that you already have in a much more flexible way:
<Border Margin="10"
Background="{Binding SelectedColor, ElementName=cp, Converter={m:Pipe First={x:Static w:MyConverters.DarkColor}, Second={x:Static w:MyConverters.ColorBrushConverter}}}"
Width="100" Height="100"/>