Colin Eberhardt has posted a neat multi-purpose value converter that tries various conventional conversion methods to convert from one type to another. This overcomes a frustrating limitation in WPF/Silverlight data binding where the XAML parser is seemingly more intelligent at converting literal values to target types than values sourced from binding expressions.

Unfortunately, the TypeConverter usage will not work in Silverlight but perhaps using the XamlReader technique there’s something that could be done?

Also, a couple of the readers in the comments had a great suggestion to derive the converter from MarkupExtension to simplify the XAML. I think I’m going to have to go back and do that with all of my converters now.

Download the example project which includes the source for SwitchConverter and SwitchedContent.

I figured I would write some more about value converters since I think there’s a lot of cool things you can do with them, especially when you put a little extra effort into generalizing them.

One thing I find I need to do often is to show content conditionally depending on a certain data binding condition. For example, let’s say we had a WeatherReport object that had a Condition property that had the following enumeration values: Sunny, Cloudy, Rain, Snow. We want to have a different image for each condition.

In WPF you can do this using a DataTrigger but even that is a bit verbose for my tastes. In Silverlight it’s a lot more difficult. The Expression Blend SDK gives you a behavior called a DataTrigger that can achieve this. But I think a value converter would do this job quite well and the markup will be the same for WPF and Silverlight. Check out the following snippet:

<Grid>
    <Grid.Resources>
        <e:SwitchConverter x:Key="WeatherIcons">
            <e:SwitchCase When="Sunny" Then="Sunny.png" />
            <e:SwitchCase When="Cloudy" Then="Cloudy.png" />
            <e:SwitchCase When="Rain" Then="Rain.png" />
            <e:SwitchCase When="Snow" Then="Snow.png" />
        </e:SwitchConverter>
    </Grid.Resources>
    <Image Source="{Binding Condition, Converter={StaticResource WeatherIcons}}" />
</Grid>

SwitchConverterDemo

The only thing that was a little bit tricky about this converter was making sure that the value being bound to could be compared against the strings in the When attribute. At first I naively just figured I could "ToString" the input value to the converter and compare that against the When values. But this is very fragile. For example, if the input were a DateTime, the values "6/25/2010" and "6/25/2010 12:00:00 AM" would not be considered equal. That’s not even considering the fact that regional settings would cause even bigger headaches.

So this implementation simply tries to use the IConvertible interface to normalize the values before comparing them. My actual implementation uses a very complex series of checks that take into account available Parse methods, TypeConverter, etc. But it’s a lot of code that I didn’t want to junk up the example with.

The only other thing worth mentioning is that the SwitchConverter has a ContentProperty attribute that allows us to specify the SwitchCase elements inline, making the resulting XAML pretty clean if you ask me. ContentPropertyAttribute is a little known and underused attribute that lets you specify which property will be set by content in between the element tags. There are a lot of places where this should probably be applied such as DataGrid.Columns, Setter.Value, etc. But now I’m going off on a tangent.

So while this solution is in no way a replacement for a good ViewModel, it does let you cleanly represent conditional content in XAML which really sits well with the OCD part of my brain that hates ugly markup.

As a bonus, I’ve also included a SwitchedContent control that makes it much easier to swap out entire elements based upon the conditional input. An example of that is shown below.

<Button Command="{Binding IncrementCommand}">
    <e:SwitchedContent Binding="{Binding Count}" Else="Overflow!">
        <e:SwitchCase When="0" Then="Zero" />
        <e:SwitchCase When="1" Then="One" />
        <e:SwitchCase When="2" Then="Two" />
        <e:SwitchCase When="3" Then="Three" />
        <e:SwitchCase When="4" Then="Four" />
        <e:SwitchCase When="5" Then="Five" />
        <e:SwitchCase When="6" Then="Six" />
        <e:SwitchCase When="7" Then="Seven" />
        <e:SwitchCase When="8" Then="Eight" />
        <e:SwitchCase When="9" Then="Nine" />
        <e:SwitchCase When="10" Then="Ten" />
    </e:SwitchedContent>
</Button>

Download the example project which includes the source for SwitchConverter and SwitchedContent.

I came across a question on StackOverflow that is a very frequently asked question regarding data binding in XAML. Given a boolean, how do you bind the opposite value to the target? The answer of course is to use a value converter (a class implementing IValueConverter) and invert the boolean in code.

I do this a lot though so I have a NegateConverter in "Josh’s Toolbox". My NegateConverter can negate a lot of things, not just booleans. For example, numeric values, Visibility, Thickness, Point, etc. Now I’ll probably never need to negate a Point but I figured what the hell. The code isn’t very pretty and if you don’t like if statements, just stop reading. It’s utilitarian, get over it.

Can you think of anything else that can be easily negated? (Click Show Source below.)

/// <summary>
/// Produces an output value that is the negative of the input.
/// </summary>
/// <remarks>
/// The built-in signed types are supported as well as a handful of other
/// commonly used types such as <see cref="T:Point"/>, <see cref="T:TimeSpan"/>,
/// <see cref="T:Thickness"/>, etc.
/// </remarks>
public sealed class NegateConverter : IValueConverter
{

    #region Fields

    /// <summary>
    /// The default singleton instance of this converter.
    /// </summary>
    public static readonly NegateConverter Default = new NegateConverter( );

    #endregion

    #region Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="T:NegateConverter"/> class.
    /// </summary>
    public NegateConverter( )
    {
    }

    #endregion

    #region Methods

    /// <summary>
    /// Converts a value.
    /// </summary>
    /// <param name="value">The value produced by the binding source.</param>
    /// <param name="targetType">The type of the binding target property.</param>
    /// <param name="parameter">The converter parameter to use.</param>
    /// <param name="culture">The culture to use in the converter.</param>
    /// <returns>
    /// A converted value. If the method returns null, the valid null value is used.
    /// </returns>
    public object Convert( object value, Type targetType, object parameter, CultureInfo culture )
    {

        if ( value == null ) {
            return null;
        }

        if ( value is double ) {
            return Negate( (double)value );
        }

        if ( value is int ) {
            return Negate( (int)value );
        }

        if ( value is bool ) {
            return Negate( (bool)value );
        }

        if ( value is long ) {
            return Negate( (long)value );
        }

        if ( value is IConvertible ) {
            return Negate( (IConvertible)value, culture );
        }

        if ( value is TimeSpan ) {
            return Negate( (TimeSpan)value );
        }

        if ( value is Point ) {
            return Negate( (Point)value );
        }

        if ( value is Thickness ) {
            return Negate( (Thickness)value );
        }

        throw new ArgumentException( "Cannot negate " + value.GetType( ) + ".", "value" );

    }

    /// <summary>
    /// Converts a value.
    /// </summary>
    /// <param name="value">The value that is produced by the binding target.</param>
    /// <param name="targetType">The type to convert to.</param>
    /// <param name="parameter">The converter parameter to use.</param>
    /// <param name="culture">The culture to use in the converter.</param>
    /// <returns>
    /// A converted value. If the method returns null, the valid null value is used.
    /// </returns>
    public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
    {
        return Convert(value, targetType, parameter, culture);
    }

    /// <summary>
    /// Negates a <see cref="T:TimeSpan"/> value.
    /// </summary>
    /// <param name="value">The value to negate.</param>
    /// <returns>The negated value.</returns>
    private static TimeSpan Negate( TimeSpan value )
    {
        return value.Negate( );
    }

    /// <summary>
    /// Negates a <see cref="T:Point"/> value.
    /// </summary>
    /// <param name="value">The value to negate.</param>
    /// <returns>The negated value.</returns>
    private static Point Negate( Point value )
    {
        return new Point(
            -value.X,
            -value.Y
        );
    }

    /// <summary>
    /// Negates a <see cref="T:Thickness"/> value.
    /// </summary>
    /// <param name="value">The value to negate.</param>
    /// <returns>The negated value.</returns>
    private static Thickness Negate( Thickness value )
    {
        return new Thickness(
            -value.Left,
            -value.Top,
            -value.Right,
            -value.Bottom
        );
    }

    /// <summary>
    /// Negates a <see cref="T:Boolean"/> value.
    /// </summary>
    /// <param name="value">The value to negate.</param>
    /// <returns>The negated value.</returns>
    private static bool Negate( bool value )
    {
        return !value;
    }

    /// <summary>
    /// Negates a <see cref="T:Int32"/> value.
    /// </summary>
    /// <param name="value">The value to negate.</param>
    /// <returns>The negated value.</returns>
    private static int Negate( int value )
    {
        return -value;
    }

    /// <summary>
    /// Negates a <see cref="T:Int64"/> value.
    /// </summary>
    /// <param name="value">The value to negate.</param>
    /// <returns>The negated value.</returns>
    private static long Negate( long value )
    {
        return -value;
    }

    /// <summary>
    /// Negates a <see cref="T:Double"/> value.
    /// </summary>
    /// <param name="value">The value to negate.</param>
    /// <returns>The negated value.</returns>
    private static double Negate( double value )
    {
        return -value;
    }

    /// <summary>
    /// Negates a <see cref="T:IConvertible"/> value by round tripping through
    /// the System.Decimal type.
    /// </summary>
    /// <param name="value">The value to negate.</param>
    /// <param name="formatProvider">The culture information.</param>
    /// <returns>The negated value as the original input type.</returns>
    private static object Negate( IConvertible value, IFormatProvider formatProvider )
    {

        TypeCode inputType = value.GetTypeCode( );

        decimal input = value.ToDecimal( formatProvider );
        decimal output = Decimal.Negate( input );

        return System.Convert.ChangeType( output, inputType, formatProvider );

    }

    #endregion

}   // class