Intro
In XAML (SilverLight / Wpf / Metro), converters are used for a variety of purposes: type casting, string formatting, calculating the scalar value of a complex object. Within the project, we can create a lot of converter classes that solve related tasks (calculating the order status and converting it to Visibility, converting the order status to Cursor, converting the boolean value to Visibility / Invisibility, etc.). Nontrivial situation: we wrote a converter for the unusually complex formatting of TimeSpan, and now we need to format Duration in the same way - we need to write a similar converter, but already with preliminary unpacking of TimeSpan from Duration. There are many options for converting strings, and all conversions will require the same set of converters.
Naturally, trying to summarize the code, we break the conversion into smaller procedures, and, as a result, we have classes of converters consisting of two lines of code used only once.
Many people do not know that to simplify the situation and reduce the number of lines of code, it is possible to combine transformations not in classes of converters, but in XAML markup, by creating chains of converters. To do this, you need to write your own abstract converter, from which we will inherit all our conversions.
Implementation
Create a converter that, in front of its own abstract transformation, performs 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 XAML markup.
Further in the class we describe the nested converter itself:
public IValueConverter Converter { get; set; }
We allow it to be an IValueConverter - this will give us the opportunity to use already existing converters as an embedded 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 the ThisType property. It declares the type of value that we expect at the input (at the output of the nested converter). The subconverter 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 there is confidence that the developed converter 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 general, we cannot know how the converter will be used and, due to the lack of typing in converters, in the worst case, we can get neither compile-time nor run-time errors, therefore I advise you to specify the internal target type as often as possible)
Usage example
In this example, we implement two simple conversions: BoolToVisibilityConverter and InvertBooleanConverter. The idea, I think, is clear: when set to true, the control will hide, if 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:
<TextBlock Text=" "> <TextBlock.Visibility> <Binding Path="IsChecked" ElementName="checkBox"> <Binding.Converter> <Converters:BoolToVisibilityConverter> <Converters:InvertBooleanConverter /> </Converters:BoolToVisibilityConverter> </Binding.Converter> </Binding> </TextBlock.Visibility> </TextBlock> <CheckBox Content=" " x:Name="checkBox" />
Link to the projectIf it is not yet clear how to use this technique, imagine the following code:
<TextBlock> <TextBlock.Text> <Binding Path="Order"> <Binding.Converter> <TakeFirstNSymbolsConverter SymbolsCount="5"> <OrderStateToStringConverter> <OrderToOrderStateConverter /> </OrderStateToStringConverter> </TakeFirstNSymbolsConverter> </Binding.Converter> </Binding> </TextBlock.Text> </TextBlock>
Here I used 3 converters: the deepest is OrderToOrderStateConverter β calculating the order status, one level up - converting the order status to a string, and the last one (the first in the code) extracting the first 5 characters from the string (There is a similar example of working with strings in the project).
Conclusion
With the ChainConverter on board, just inherit 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 we have one type of conversion in the code once. If we use the same combination of converters more than once - it remains appropriate to declare a new class. Thanks to the described approach, we can describe the new converter in XAML markup.