Software developmentWPF

Value conversion in WPF

WPF is the top-notch UI technology from Microsoft, that finally enables us to properly separate business logic and UI from each other. There is plenty of material and documentation about it on the internet, so I won’t bother giving a general overview in this article. If you know WPF, then you already know what it is.

You may have noticed that the initial learning curve of WPF is crazy steep, since it’s such a huge and powerful framework. Especially the part where backend and frontend are separated cleanly is very hard to get right in the beginning. The concept behind this is called MVVM, where the goal is to design the entire UI in XAML, using as little code-behind as possible. One of the techniques that WPF offers is data binding, which is essential for the task of splitting the layers. Data binding also includes the possibility of automated conversion of data back and forth between the UI and the back-end. This article will shed some light on value converters in WPF and explain how to use them correctly. The full source code to all the examples in this post is available on GitHub. There is also a more enhanced version of the code in a separate repository, which is fully tested and documented and that is released as a NuGet package.

Converter madness

I have worked in client projects with hundreds of converter implementations, that do the same conversion with just minor differences. Sometimes, there even are identical conversions being done in two different implementations, when everyone involved has lost count of what already exists. And that’s not just a rare oddity, I see this a lot. Developers don’t seem to be interested in becoming acquainted with converters at all. This is weird, as they are actually easy to understand and there is not much to it to get it right.

Today, I will start with a simple example, that demonstrates the purpose of value converters in WPF bindings. The initial goal is a TextBlock that will tell whether a Button is currently pressed or not. A specialized converter is used to bind a string to the Text property of the text block, depending on the button’s boolean property IsPressed.

...

<Grid>
    <Grid.Resources>
        <ResourceDictionary>
            <local:BooleanToStringConverter x:Key="BooleanToStringConverter" />
        </ResourceDictionary>
    </Grid.Resources>

    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>
        <RowDefinition />
    </Grid.RowDefinitions>

    <Border
        Grid.Column="0" Grid.Row="0"
        Margin="10" Padding="10" BorderThickness="5">

        <TextBlock
            VerticalAlignment="Center" HorizontalAlignment="Center"
            Text="{Binding ElementName=Button, Path=IsPressed, Converter={StaticResource BooleanToStringConverter}}" />
    </Border>

    <Button
        x:Name="Button"
        Grid.Column="1" Grid.Row="0"
        Margin="10" Padding="10"
        Content="Press Me!"/>
</Grid>

...
public class BooleanToStringConverter : IValueConverter {
    public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) {
        if(value != null && value is Boolean boolean) {
            return boolean ? "Pressed." : "Not pressed.";
        }
        return "I'm broken :-(";
    }

    public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) => throw new NotImplementedException();
}

Avoiding exceptions

There is a debate out there on how an impossible conversion should be handled. Throwing a NotImplementedException is a common way, but this will result in an unhandled runtime exception. Returning Binding.DoNothing is also famous but problematic, since it will abort the evaluation of the binding completely. Microsoft advertises returning DependencyProperty.UnsetValue to signal the binding engine, that a conversion hasn’t worked out.

public class BooleanToStringConverter : IValueConverter {
    public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) {
        if(value != null && value is Boolean boolean) {
            return boolean ? "Pressed." : "Not pressed.";
        }
        return DependencyProperty.UnsetValue;
    }

    public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) => DependencyProperty.UnsetValue;
}

More colours and more converters

I want to manipulate the colour as well and make the pressed state a lot more obvious. The problem here is that a specific converter implementation is required for every colour combination. This is the reason behind the thousands of lines of code behind value converters in some WPF projects around the world. Another big issue is the repeating code, that is likely being copied and pasted into new converters all the time. Fixing a bug there is a tedious task at best.

public class BooleanToBorderBrushConverter : IValueConverter {
    public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) {
        if(value != null && value is Boolean boolean) {
            return boolean ? Brushes.Lime : Brushes.WhiteSmoke;
        }
        return DependencyProperty.UnsetValue;
    }

    public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) => DependencyProperty.UnsetValue;
}
public class BooleanToBackgroundBrushConverter : IValueConverter {
    public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) {
        if(value != null && value is Boolean boolean) {
            return boolean ? Brushes.Magenta : Brushes.LightGray;
        }
        return DependencyProperty.UnsetValue;
    }

    public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) => DependencyProperty.UnsetValue;
}

The resource dictionary also grows with every new conversion that is required for the UI to work. This simple example already uses three different converters, and I think it’s safe to say that a more complex UI has a lot more conversion to do.

...

<Grid>
    <Grid.Resources>
        <ResourceDictionary>
            <conv:BooleanToStringConverter x:Key="BooleanToStringConverter" />
            <conv:BooleanToBorderBrushConverter x:Key="BooleanToBorderBrushConverter" />
            <conv:BooleanToBackgroundBrushConverter x:Key="BooleanToBackgroundBrushConverter" />
        </ResourceDictionary>
    </Grid.Resources>

...

    <Border
        Grid.Column="0" Grid.Row="0"
        Margin="10" Padding="10" BorderThickness="5"
        BorderBrush="{Binding ElementName=Button, Path=IsPressed, Converter={StaticResource BooleanToBorderBrushConverter}}"
        Background="{Binding ElementName=Button, Path=IsPressed, Converter={StaticResource BooleanToBackgroundBrushConverter}}">

...

Reducing boiler-plate code

Boolean converters make for a great example of creating a generic base converter. The logic is always identical and a generic secondary type gives it all the flexibility that is needed. Adding the configuration properties True and False to the base class lets a consumer specify the exact conversion behaviour in any scenario. Conversion back and forth is handled in one place and can be adapted and tested easily. There is still a need for an individual implementation of specific types, as generics can’t be used in XAML, but the required effort for a concrete converter is limited to the constructors.

public class BooleanConverter<T> : IValueConverter {
    public T True { get; set; }
    public T False { get; set; }

    public BooleanConverter(T @true, T @false) {
        True = @true;
        False = @false;
    }

    public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) {
        if(value != null && value is Boolean boolean) {
            return boolean ? True : False;
        }
        return DependencyProperty.UnsetValue;
    }

    public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) {
        if(value != null && value is T t) {
            if(True.Equals(t)) { return true; }
            if(False.Equals(t)) { return false; }
        }
        return DependencyProperty.UnsetValue;
    }
}
public class BooleanToStringConverter : BooleanConverter<String> {
    public BooleanToStringConverter() : this("true", "false") { }
    public BooleanToStringConverter(String @true, String @false) : base(@true, @false) { }
}
public class BooleanToBrushConverter : BooleanConverter<Brush> {
    public BooleanToBrushConverter() : this(Brushes.Green, Brushes.Red) { }
    public BooleanToBrushConverter(Brush @true, Brush @false) : base(@true, @false) { }
}

The resource dictionary hasn’t changed much in size, but the behaviour of every listed converter is much more apparent now. Anyone can easily make out the return values at a glance.

...

<Grid.Resources>
    <ResourceDictionary>
        <conv:BooleanToStringConverter x:Key="BooleanToStringConverter" True="Pressed." False="Not pressed." />
        <conv:BooleanToBrushConverter x:Key="BooleanToBorderBrushConverter" True="Lime" False="WhiteSmoke" />
        <conv:BooleanToBrushConverter x:Key="BooleanToBackgroundBrushConverter" True="Magenta" False="LightGray" />
    </ResourceDictionary>
</Grid.Resources>

...

Getting rid of resource dictionaries

The generic base converter is already a step in the right direction, but we can do better still. By having the converters inherit from the class MarkupExtension as well, they can be instantiated and used just like any other object in XAML. And since the BooleanConverter<T> will most likely not be the only generic implementation, another layer can be added beneath it. An abstract base must implement the method MarkupExtension.ProvideValue for the type to be usable from within XAML.

public abstract class BaseValueConverter : MarkupExtension, IValueConverter {
    public abstract Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture);

    public abstract Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture);

    public override Object ProvideValue(IServiceProvider serviceProvider) => this;
}
public class BooleanConverter<T> : BaseValueConverter {

    // ...

}

The resource dictionary at the top of the file is finally gone. It is still possible to create converters as static resources, but it’s not required any more. In addition, converters can now be instantiated and configured from within every binding. And nothing stops a developer from creating converters as static resources, if they are used multiple times around the XAML code.

...

<Border
    Grid.Column="0" Grid.Row="0"
    Margin="10" Padding="10" BorderThickness="5"
    BorderBrush="{Binding ElementName=Button, Path=IsPressed, Converter={conv:BooleanToBrushConverter True=Lime, False=WhiteSmoke}}"
    Background="{Binding ElementName=Button, Path=IsPressed, Converter={conv:BooleanToBrushConverter True=Magenta, False=LightGray}}">

    <TextBlock
        VerticalAlignment="Center" HorizontalAlignment="Center"
        Text="{Binding ElementName=Button, Path=IsPressed, Converter={conv:BooleanToStringConverter True='Pressed.', False='Not pressed.'}}" />
</Border>

...

Laying the foundation for more conversions

As already mentioned, the boolean base converter isn’t the only one around. Every conversion that has a fixed set of possible input values, can be made into a useful base converter. Nullable boolean input springs to mind, or a converter that checks for null references. And whatever converter is implemented on top of these, it only takes two to three lines of code to declare the class and the required constructors.

public class NullableBooleanConverter<T> : BaseValueConverter {
    public T True { get; set; }
    public T False { get; set; }
    public T Null { get; set; }

    public NullableBooleanConverter(T @true, T @false, T @null) {
        True = @true;
        False = @false;
        Null = @null;
    }

    public override Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) {
        if(value == null) { return Null; }
        if(value is Boolean boolean) {
            return boolean ? True : False;
        }
        return DependencyProperty.UnsetValue;
    }

    public override Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) {
        if(value != null && value is T t) {
            if(True.Equals(t)) { return true; }
            if(False.Equals(t)) { return false; }
            if(Null.Equals(t)) { return null; }
        }
        return DependencyProperty.UnsetValue;
    }
}
public class NullConverter<T> : BaseValueConverter {
    public T Null { get; set; }
    public T NotNull { get; set; }

    public NullConverter(T @null, T notNull) {
        Null = @null;
        NotNull = notNull;
    }

    public override Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) => value == null ? Null : NotNull;

    public override Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) => DependencyProperty.UnsetValue;
}

Converting multiple values at once

If a binding has multiple preconditions then WPF has us covered as well with multi binding and multi value converters. It’s basically the same story as before, but now the conversion isn’t one-to-one but many-to-one. One important difference is that the ConvertBack method should return null in case there is no clear conversion back. The concrete implementations for the types String and Brush are no different from what we did before, except for the base class.

public abstract class BaseMultiValueConverter : MarkupExtension, IMultiValueConverter {
    public abstract Object Convert(Object[] values, Type targetType, Object parameter, CultureInfo culture);

    public abstract Object[] ConvertBack(Object value, Type[] targetTypes, Object parameter, CultureInfo culture);

    public override Object ProvideValue(IServiceProvider serviceProvider) => this;
}
public class BooleanAndConverter<T> : BaseMultiValueConverter {
    public T True { get; set; }
    public T False { get; set; }

    public BooleanAndConverter(T @true, T @false) {
        True = @true;
        False = @false;
    }

    public override Object Convert(Object[] values, Type targetType, Object parameter, CultureInfo culture) {
        if(values.Any(value => value == null || !(value is Boolean))) { return DependencyProperty.UnsetValue; }
        return values.Cast<Boolean>().All(value => value) ? True : False;
    }

    public override Object[] ConvertBack(Object value, Type[] targetTypes, Object parameter, CultureInfo culture) => null;
}

Using multi binding in XAML is a little different, and it takes more code to declare in stock WPF. However, a little test proves that it works as expected and that it is indeed possible to merge multiple preconditions into a single converter result.

<Border
    Grid.Column="0" Grid.Row="2"
    Margin="10" Padding="10" BorderThickness="5">
    <Border.BorderBrush>
        <MultiBinding Converter="{mconv:BooleanAndToBrushConverter True=Green, False=WhiteSmoke}">
            <Binding ElementName="Button" Path="IsPressed" />
            <Binding ElementName="CheckBox" Path="IsChecked" />
        </MultiBinding>
    </Border.BorderBrush>
    <Border.Background>
        <MultiBinding Converter="{mconv:BooleanAndToBrushConverter True=Lime, False=LightGray}">
            <Binding ElementName="Button" Path="IsPressed" />
            <Binding ElementName="CheckBox" Path="IsChecked" />
        </MultiBinding>
    </Border.Background>

    <TextBlock
        VerticalAlignment="Center" HorizontalAlignment="Center">
        <TextBlock.Text>
            <MultiBinding Converter="{mconv:BooleanAndToStringConverter True='AND: True.', False='AND: False.'}">
                <Binding ElementName="Button" Path="IsPressed" />
                <Binding ElementName="CheckBox" Path="IsChecked" />
            </MultiBinding>
        </TextBlock.Text>
    </TextBlock>
</Border>

We can still do a lot better and inline the multi binding declaration, but it requires a little ingenuity for this to work. By default, the multi binding object cannot be fed binding objects directly in the declaration, since it only has a parameterless constructor. The issue is solved with a new MultiBinding class that inherits from the original WPF version and that provides a wide range of constructors as well.

using BB = System.Windows.Data.BindingBase;

public class MultiBinding : System.Windows.Data.MultiBinding {
    public MultiBinding(params BB[] bindings) {
        Array.ForEach(bindings, _ => Bindings.Add(_));
    }

    public MultiBinding(BB b1) : this(new BB[] { b1 }) { }

    // ctor implementations with parameter count 2 to 7

    public MultiBinding(BB b1, BB b2, BB b3, BB b4, BB b5, BB b6, BB b7, BB b8) : this(new BB[] { b1, b2, b3, b4, b5, b6, b7, b8 }) { }
}

The necessary XAML code has been condensed into a very compact format without losing any flexibility, and the required C# code is very clean and simple too.

<Border
    Grid.Column="0" Grid.Row="2"
    Margin="10" Padding="10" BorderThickness="5"
    BorderBrush="{bind:MultiBinding {Binding ElementName=Button, Path=IsPressed}, {Binding ElementName=CheckBox, Path=IsChecked},
        Converter={mconv:BooleanAndToBrushConverter True=Green, False=WhiteSmoke}}"
    Background="{bind:MultiBinding {Binding ElementName=Button, Path=IsPressed}, {Binding ElementName=CheckBox, Path=IsChecked},
        Converter={mconv:BooleanAndToBrushConverter True=Lime, False=LightGray}}">

    <TextBlock Text="{bind:MultiBinding {Binding ElementName=Button, Path=IsPressed}, {Binding ElementName=CheckBox, Path=IsChecked},
            Converter={mconv:BooleanAndToStringConverter True='AND: True', False='AND: False'}}"
        VerticalAlignment="Center" HorizontalAlignment="Center">
    </TextBlock>
</Border>

I’m sorry that this multi value converter topic is such a drag, but we’re not quite there yet. We’ve basically created a logic AND gate, and there are at least 5 more useful gates to create next to it. There should be a single converter, that is configurable as any of the six gates from within the XAML code.

public class LogicalGateConverter<T> : BaseMultiValueConverter {
    public delegate Boolean GateLogic(IEnumerable<Boolean> values);

    public GateLogic Logic { get; private set; }
    public LogicalGates Gate { set => Logic = GetLogicByGate(value); }
    public T True { get; set; }
    public T False { get; set; }

    public LogicalGateConverter(LogicalGates gate, T @true, T @false) {
        Gate = gate;
        True = @true;
        False = @false;
    }

    public LogicalGateConverter(GateLogic logic, T @true, T @false) {
        Logic = logic;
        True = @true;
        False = @false;
    }

    public override Object Convert(Object[] values, Type targetType, Object parameter, CultureInfo culture) {
        if(values.Any(value => value == null || !(value is Boolean))) { return DependencyProperty.UnsetValue; }
        return Logic(values.Cast<Boolean>()) ? True : False;
    }

    public override Object[] ConvertBack(Object value, Type[] targetTypes, Object parameter, CultureInfo culture) => null;

    private GateLogic GetLogicByGate(LogicalGates gate)
        => gate switch {
            LogicalGates.And => new GateLogic((values) => values.All(_ => _)),
            LogicalGates.Nand => new GateLogic((values) => values.Any(_ => !_)),
            LogicalGates.Or => new GateLogic((values) => values.Any(_ => _)),
            LogicalGates.Nor => new GateLogic((values) => values.All(_ => !_)),
            LogicalGates.Xor => new GateLogic((values) => values.Count(value => value) == 1),
            LogicalGates.Xnor => new GateLogic((values) => values.Count(value => value) != 1),
            _ => new GateLogic((_) => false),
        };
}
public enum LogicalGates {
    And,
    Nand,
    Or,
    Nor,
    Xor,
    Xnor
}
public class LogicalGateToBooleanConverter : LogicalGateConverter<Boolean> {
    public LogicalGateToBooleanConverter() : this(LogicalGates.And) { }

    public LogicalGateToBooleanConverter(LogicalGates gate) : this(gate, true, false) { }
    public LogicalGateToBooleanConverter(GateLogic logic) : this(logic, true, false) { }

    public LogicalGateToBooleanConverter(LogicalGates gate, Boolean @true, Boolean @false) : base(gate, @true, @false) { }
    public LogicalGateToBooleanConverter(GateLogic logic, Boolean @true, Boolean @false) : base(logic, @true, @false) { }
}

Alright, this is finally it. A single multi value converter that does all the logical gates and that can be configured directly from within the instance creation in XAML. Any developer can inject a custom GateLogic delegate into the constructor, to modify the behaviour and adapt the converter. In case the Boolean return value isn’t enough, creating new concrete implementations for other types like String and Brush works the same way. There is nothing left to wish for.

<Border
    Grid.Column="0" Grid.Row="2"
    Margin="10" Padding="10" BorderThickness="5"
    BorderBrush="{bind:MultiBinding {Binding ElementName=Button, Path=IsPressed}, {Binding ElementName=CheckBox, Path=IsChecked},
        Converter={mconv:LogicalGateToBrushConverter Gate=And, True=Green, False=WhiteSmoke}}"
    Background="{bind:MultiBinding {Binding ElementName=Button, Path=IsPressed}, {Binding ElementName=CheckBox, Path=IsChecked},
        Converter={mconv:LogicalGateToBrushConverter Gate=And, True=Lime, False=LightGray}}">

    <TextBlock Text="{bind:MultiBinding {Binding ElementName=Button, Path=IsPressed}, {Binding ElementName=CheckBox, Path=IsChecked},
            Converter={mconv:LogicalGateToStringConverter Gate=And, True='AND: True', False='AND: False'}}"
        VerticalAlignment="Center" HorizontalAlignment="Center">
    </TextBlock>
</Border>

Chaining converters

Obviously, that last bit was a lie. I still have plenty on my wish list. One of those things is being able to chain converters. I don’t always want to create a new A to C converter, if I can go from A to B and then to C instead. In this case, the Chain is just another converter without any conversion logic by itself. Instead, it can take a range of Link instances with conversion information that are called one after the other. Having an InvertedBooleanConverter – also known as a NOT gate – and even a pipe through NoOpConverter can be helpful in this scenario.

public partial class Chain : BaseValueConverter {
    public Collection<Link> Links { get; } = new Collection<Link>();

    public Chain(params Link[] links) {
        Array.ForEach(links, _ => Links.Add(_));
    }

    public Chain(Link l1) : this(new Link[] { l1 }) { }

    // ctor implementation with parameter count 2 to 7

    public Chain(Link l1, Link l2, Link l3, Link l4, Link l5, Link l6, Link l7, Link l8) : this(new Link[] { l1, l2, l3, l4, l5, l6, l7, l8 }) { }

    public override Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) {
        Object returnObject = value;
        foreach(Link link in Links) {
            returnObject = link.Converter.Convert(returnObject, targetType, link.Parameter, link.Culture);
        }
        return returnObject;
    }

    public override Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) {
        Object returnObject = value;
        foreach(Link link in Links.Reverse()) {
            returnObject = link.Converter.ConvertBack(returnObject, targetType, link.Parameter, link.Culture);
        }
        return returnObject;
    }
}
public class Link : MarkupExtension {
    public IValueConverter Converter { get; set; }
    public Object Parameter { get; set; }
    public CultureInfo Culture { get; set; }

    public override Object ProvideValue(IServiceProvider serviceProvider) => this;
}
public class NoOpConverter : BaseValueConverter {
    public override Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) => value;
    public override Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) => value;
}
public class InvertedBooleanConverter : BooleanConverter<Boolean> {
    public InvertedBooleanConverter() : base(false, true) { }
}

I realize that this specific usage isn’t incredibly clever, and the conversion is a lot easier with a customized BooleanToVisibilityConverter. But it shows how converters can be chained properly to get new results.

<Border
    Grid.Column="0" Grid.Row="5"
    Margin="10" Padding="10" BorderThickness="5"
                Visibility="{Binding ElementName=Button, Path=IsPressed,
        Converter={conv:Chain {conv:Link Converter={conv:NoOpConverter}}, {conv:Link Converter={conv:BooleanToVisibilityConverter}}}}"
    BorderBrush="Green" Background="Lime">

    <TextBlock Text="Pressed." VerticalAlignment="Center" HorizontalAlignment="Center" />
</Border>

<Border
    Grid.Column="0" Grid.Row="5"
    Margin="10" Padding="10" BorderThickness="5"
    Visibility="{Binding ElementName=Button, Path=IsPressed,
        Converter={conv:Chain {conv:Link Converter={conv:InvertedBooleanConverter}}, {conv:Link Converter={conv:BooleanToVisibilityConverter}}}}"
    BorderBrush="WhiteSmoke" Background="LightGray">

    <TextBlock Text="Not pressed." VerticalAlignment="Center" HorizontalAlignment="Center" />
</Border>

Debugging

With all this binding going on, it’s easy to miss something important. The binding engine is nice enough to tell us, if we are trying to bind against a non-existing property. But sometimes, forgetting the ElementName has the same effect as setting the DataContext to null, and there won’t be a message at all. A debug converter comes in handy if it isn’t clear whether the binding is being evaluated or not.

public class DebugConverter : BaseValueConverter {
    public override Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) {
        Debugger.Break();
        return value;
    }

    public override Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) {
        Debugger.Break();
        return value;
    }
}

Decorations and finish line

This is the end of the journey for value converters, and it’s finally time to decorate the place for the big party. In the world of C#, this means adding attributes, and in our specific case it’s the ValueConversionAttribute. The attribute helps other tools to take educated guesses about what the converter does and how it may be useful, so it’s good to have it.

In conclusion, any WPF project should have a converter library somewhere at the core, for every developer in the project to take advantage of. This helps in keeping the overall converter count low and prevents duplicate implementations. It also makes for a much cleaner XAML code that is easier to read and comprehend. The example code from this article should be good to use and is up for grabs for anyone.

Leave a Reply

Your email address will not be published. Required fields are marked *