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

Posts
Great piece of information! Thanks for sharing the code
2010-06-23 @ 4:15 am
Here’s what I don’t get, though — and maybe I’m just missing something really fundamental somewhere…
I find myself sometimes having to do things like “set Visibility to Visible on Control A if P”, which entails often making a “BoolToVisible” converter. But then somewhere else I have to do “set Visibility to Visible on Control B if not P” — for which I wind up writing another, almost but not quite identical, “inverseBoolToVisible” converter.
Is there a way to avoid writing the extra converter?
-mpg
2010-06-23 @ 10:29 am
Handy converter. Thanks.
@mpg: it’s not that hard: just write one, give it 2 properties: OnTrue and OnFalse (which can be of a specific type, for instance Visibility, but also just a generic Object so that you can have a bool2something converter).
When you convert, you just do: (bool)value ? OnTrue : OnFalse
In XAML, that becomes:
or
Don’t forget to give them meaningful defaults, so that you can omit them for common scenarios.
2010-06-23 @ 2:21 pm
Great tip, Inferis. I was going to respond similarly because I am going to post next about my SwitchConverter class that does just that. The tricky part about that one is making it generic enough to support conversion from string to targetType without hard coding too many common conversions (such as Boolean to Visibility).
Update: Nice! I just realized that if just pass the strings back from the converter, the binding system will take care of converting them to the target type. In other words <BoolConverter True=”Green” False=”Red” /> will get converted to SolidColorBrush automatically.
2010-06-24 @ 2:58 pm