XAML: Nested Converters
Intro
In XAML (SilverLight / Wpf / Metro) converters are used for a variety of purposes: type conversion, formatting strings, calculating the scalar value of a complex object. Within the framework of the project, we can create a lot of converter classes that solve related problems (calculating the order status and converting it to Visibility, converting the order status to Cursor, converting a Boolean value to Visibility / Invisibility, etc.). A non-trivial situation: we wrote a converter for unusually complex TimeSpan formatting, and now you need to format Duration in the same way - you need to write a similar converter, but with preliminary unpacking of TimeSpan from Duration. There can be many options for converting strings, and all conversions will require the same many converters.
Naturally, trying to generalize the code, we break the conversion into smaller procedures, and, as a result, we encounter converter classes consisting of two lines of code that are used only once.
Many do not know that in order to simplify the situation and reduce the number of lines of code, it is possible to combine conversions not in converter classes, but in XAML markup, by creating converter chains. To do this, write your abstract converter, from which we will inherit all our transformations.
Implementation
Let's create a converter that, before its own abstract conversion, will perform the conversion of another, nested converter.
Class declaration:
[ContentProperty("Converter")]
public abstract class ChainConverter : IValueConverter
ContentProperty attribute - Specifies the property that will be used implicitly in the XAML markup.
Next, in the class, we describe the embedded converter itself:
public IValueConverter Converter { get; set; }
We allow it to be IValueConverter - this will give us the opportunity to use existing converters as a nested one.
The conversion code is simple:
object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (Converter != null)
value = Converter.Convert(value, ThisType ?? targetType, parameter, culture);
return Convert(value, targetType, parameter, culture);
}
public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture);
I will explain thisType property. It declares the type of value that we expect at the input (at the output of the nested converter). The sub-converter may be able to calculate values of different types, and if in the case of simple binding it is ideal (the same converter is used for different target types), then in our case we most likely do not want the nested converter to know the type the ultimate goal. If you are sure that the converter you are developing will not be used as a container for other converters that change their behavior depending on the targetType value passed from the binding, then you can not override this property - by default it will return null. (In the general case, we cannot know how the converter will be used and, due to the lack of typing in the converters, in the worst case, we cannot receive either compile-time or run-time errors,
Usage example
In this example, we implement two simple conversions: BoolToVisibilityConverter and InvertBooleanConverter. The idea, I think, is understandable: when set to true, the control will hide, when set to false, it will be shown.
BoolToVisibilityConverter Code:
public class BoolToVisibilityConverter : ChainConverter
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool) value ? Visibility.Visible : Visibility.Collapsed;
}
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
InvertBooleanConverter Code:
public class InvertBooleanConverter : ChainConverter
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return !(bool) value;
}
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Using:
Link to the project
If it is not yet clear how to use this technique, imagine this code:
Here I used 3 converters: the deepest - OrderToOrderStateConverter - calculating the order status, one level up - converting the order status into a string, and the last (first in the code) - extracting the first 5 characters from the string (The project has a similar example of working with strings).
Conclusion
With ChainConverter on board, just inherit the new converters from it. And at some point, instead of creating a new converter, you only need to combine two or more already implemented ones.
This approach makes life much easier if some type of conversion occurs once in the code. If we use the same combination of converters more than once, it remains advisable to declare a new class. Thanks to the described approach, we can describe the new converter in XAML markup.