Introduction

This article examines how to combine multiple physical value converters into one logical value converter, in the context of data binding in the Windows Presentation Foundation. It is expected that the reader is already familiar with data binding in WPF and the use of XAML to declare user interface elements. This article does not explain the fundamentals of WPF data binding, but the reader can refer to this article in the Windows SDK for a comprehensive overview of WPF data binding, if necessary.

The code presented in this article was compiled and tested against the June 2006 CTP of the .NET Framework 3.0.

Background

The data binding infrastructure of the WPF is extremely flexible. One of the major contributors to that flexibility is the fact that a custom value converter can be injected between two bound objects (i.e. the data source and target). A value converter can be thought of as a black box into which a value is passed, and another value is emitted.

A value converter is any object which implements the IValueConverter interface. That interface exposes two methods: Convert and ConvertBack. Convert is called when a bound value is being passed from the data source to the target, and ConvertBack is called for the inverse operation. If a value converter decides that it cannot return a meaningful output value based on the input value, it can return Binding.DoNothing, which will inform the data binding engine to not push the output value to the binding operation’s respective target.

The Problem

The WPF data binding support allows a Binding object to have one value converter, which can be assigned to its Converter property. Having a single converter for a binding operation is limiting because it forces your custom value converter classes to be very specific. For example, if you need to base the color of some text in the user interface on the value of a numeric XML attribute, you might be inclined to make a value converter which converts the XML attribute value to a number, then maps that number to an enum value, then maps that enum value to a Color, and finally creates a Brush from that color. This technique would work, but the value converter would not be reusable in many contexts.

It would be better if you could create a library of modular value converters and then somehow pipe them together, like how most command-line environments allow the output of one command to be piped into another command as input.

The Solution

In response to this problem, I created a class called ValueConverterGroup. The ValueConverterGroup class is a value converter (it implements IValueConverter) which allows you to combine multiple value converters into a set. When the ValueConverterGroup’s Convert method is invoked, it delegates the call to the Convert method of each value converter it contains. The first converter added to the group is called first, and the last converter added to the group is called last. The opposite occurs when the ConvertBack method is called.

The output of one value converter in the group becomes the input of the next value converter, and the output of the last value converter is returned to the WPF data binding engine as the output value of the entire group. For the sake of convenience, I made it possible to declare a ValueConverterGroup and its child value converters directly in a XAML file.

Using the Code

The following is a short demo which demonstrates how to use the ValueConverterGroup class. The entire demo is available for download at the top of this article.

Here is the simple XML data used in the demo:

<?xmlversion="1.0"encoding="utf-8"?><Tasks><TaskName="Paint the living room"Status="0"/><TaskName="Wash the floor"Status="-1"/><TaskName="Study WPF"Status="1"/></Tasks>

<!-- Converts the Status attribute text to a SolidColorBrush used to draw
the output of statusDisplayNameGroup. --><local:ValueConverterGroupx:Key="statusForegroundGroup"><local:IntegerStringToProcessingStateConverter/><local:ProcessingStateToColorConverter/><local:ColorToSolidColorBrushConverter/></local:ValueConverterGroup>

<!-- Converts the Status attribute to the tooltip message for
that processing state. --><local:ValueConverterGroupx:Key="statusDescriptionGroup"><local:XmlAttributeToStringStateConverter/><local:IntegerStringToProcessingStateConverter/><local:EnumToDescriptionConverter/></local:ValueConverterGroup>

The window declared above looks like this (notice that the color of the task's status text depends on the status value):

As you can see in the XAML above, the demo project creates several ValueConverterGroups. The one which determines the foreground of the status text contains three child value converters. The first converter converts the Status attribute value from a number to a ProcessingState enum value. The next converter maps the enum value to a Color which is used to graphically represent that processing state. The last converter in the group creates a SolidColorBrush from the color emitted by the previous converter.

The aggregated approach to value conversion presented above makes it possible for value converters to remain simple and have an easily defined purpose. This large advantage comes with a very small price. There is one requirement imposed on the value converters used in a ValueConverterGroup. The value converter class must be decorated with the System.Windows.Data.ValueConversionAttribute attribute exactly once. That attribute is used to specify the data type the converter expects the input value to be, and the data type of the object it will emit.

To understand why this restriction exists, it is necessary to look under the covers at how the ValueConverterGroup class works and the requirements that it must satisfy.

How it Works

The remainder of this article discusses how the ValueConverterGroup class works. You do not need to read this section in order to use the class.

The ValueConverterGroup class is relatively simple. It merely delegates calls to its Convert and ConvertBack methods off to the value converters it contains. There were two aspects of creating this class that required some extra planning to get right. First let’s examine its implementation of IValueConverter.Convert:

The process of converting the input value to an output value requires us to call into every value converter in the group. That’s simple. The problem is that each value converter has certain expectations regarding the targetType argument. The overall conversion process might require that the output value is a Brush, but the intermediate converters in the group might have completely different expectations for the type of object they are supposed to emit.

For example, the demo shown in the previous section of this article converts a string (which contains an integral value) to a SolidColorBrush. Along the way, it converts the integer to a ProcessingState enum value, and then that value to a Color, and finally the Color gets turned into a SolidColorBrush. Only the last converter in the group expects to be emitting a brush, so only that converter should receive the original target type value which was passed into the ValueConverterGroup’s Convert method.

The solution to this problem is to require that all value converters added to the group are decorated with the ValueConversionAttribute. Here is the code that enforces this requirement:

When a value converter is added to the Converters property (not shown above) the OnConvertersCollectionChanged method is executed, and it will throw an exception if any of the converters are not decorated with the ValueConversionAttribute. For performance reasons, the attribute instance tied to the value converter is cached once it has been retrieved.

Since each value converter in the group is guaranteed to explain what types it expects to deal with, the Convert method can determine the target type for each converter. As seen in the Convert method above, the following method is called before a value converter is executed:

That method simply checks to see if the converter about to executed is the last or first in the group. If the Convert method is being called and the current converter is not the last one in the group, the target type value is retrieved from SourceType property on the ValueConversionAttribute instance associated with the next converter in the list (the order of converter execution reverses when ConvertBack is called). If ConvertBack is executing, the TargetType of the previous converter becomes the target type for the current converter. Note, the source and target semantics are swapped when dealing with ConvertBack.

The second aspect of creating this class that was not immediately obvious to me was how to enable value converters to be easily added to the group in XAML. This was not exactly a difficult piece of code to write, it just took me a while to find how to do it

Basically, that attribute informs the WPF infrastructure that the Converters property in this class is the property to add items to when adding child objects in XAML. Adding the ContentPropertyAttribute to the class makes it possible to use the ValueConverterGroup class in XAML like so:

Conclusion

By piping together value converters it is much easier to make them reusable in a number of ways.

Every value converter added to a ValueConverterGroup must be decorated exactly once with the ValueConversionAttribute.

When the Convert operation occurs, the value converters in the group are executed in the order that they exist in the Converters collection. When ConvertBack is called, they are executed last-to-first.