Combining Value Converters in Silverlight and WPF

On Silverlight and WPF projects I find my self spending a lot of time creating value converters, yes, the IValueConverter classes used for binding in XAML when you need help mapping a property from a ViewModel class to a control dependency property. Sometimes you just need a negate a Boolean value for the IsEnabled property. Often it’s the Visibility value that needs to be set based on a Boolean, or it could be that you want to set the visibility of a control based whether or not a string is null or empty, or whether or not a ViewModel property is null. You merrily spank out an IValueConverter implementing class tailored for each data binding situation as you encounter them. Before you know it, you have a piles of these IValueConverter derived critters littered all throughout your code.

There are ways that you can tame the IValueConverter mess. One approach is shown in this blog here: http://www.garethevans.com/2010/07/linking-multiple-value-converters-in-wpf-and-silverlight/. Here, a converter is made to contain a list of child converters to convert the value of a sequence of steps. In this solution, you could create a converter to convert null/non null value into a Boolean, then create another converter that converts the Boolean into a Visibility. You could also create a negator converter to negate the final Boolean value if need be. Just string the value converters together in a list and you can combine and reuse simpler value converters for more complex scenarios. This example only implements the IValueConverter.Convert method, IValueConverter.ConvertBack is not supported.

Another article http://www.codeproject.com/KB/WPF/PipingValueConverters_WPF.aspx uses a similar approach. This author supports the ConvertBack operation, unlike the first. When I looked at the author’s logic for this article, I noticed a flaw in my own code regarding the ConvertBack logic. You need to call the converters in reverse order than for the Convert logic.

In this post I present a similar approach, but nstead of a top level value converter that iterates through a list of child converters, I create an IValueConverter base class that can reference another IValueConverter via a Link property. The converters form a linked list, and values are piped up and down this llinked list chain as they are converted and converted back. If Link property is set, it first calls the converter it is linked to, then converts the resultant value. Using this approach way, a converters can be linked together to form a chain. Below is the abstract LinkedConverter class from which to derive concrete instance from:

    /// <summary>
    /// Base class for all linked converters.
    /// </summary>
    public abstract class LinkedConverter : IValueConverter
    {
        private IValueConverter _linkedConverter = null;
        private Type _linkConvertOverrideType = null;
        private Type _convertBackOverrideType = null;

        /// <summary>
        /// A reference to a <see cref="LinkedConverter"/> to process the value before this instance process the value in <see cref="IValueConverter.Convert"/>.
        /// </summary>
        /// <remarks>
        /// For the <see cref="IValueConverter.ConvertBack"/> operation, this instance process the value before hand.
        /// </remarks>
        public IValueConverter Link
        {
            get { return _linkedConverter; }
            set
            {
                _linkedConverter = value;
            }
        }

        /// <summary>
        /// An optional <see cref="System.Type"/> value to pass to the <see cref="InstanceConvert"/> method of the chained coverter if the <see cref="Link"/>
        /// property is set.
        /// </summary>
        /// <remarks>
        /// Normally this value is ignored as it is in most implementations of <see cref="IValueConverter.Convert"/>.
        /// </remarks>
        [TypeConverter(typeof(StringToTypeConverter))]
        public Type OverrideType
        {
            get { return _linkConvertOverrideType; }
            set
            {
                _linkConvertOverrideType = value;
            }
        }

        /// <summary>
        /// An optional <see cref="System.Type"/> value to pass to the <see cref="InstanceConvertBack"/> method of this instance if the <see cref="Link"/>
        /// property is set.
        /// </summary>
        /// <remarks>
        /// Normally this value is ignored as it is in most implementations of <see cref="IValueConverter.ConvertBack"/>.
        /// </remarks>
        [TypeConverter(typeof(StringToTypeConverter))]
        public Type BackOverrideType
        {
            get { return _convertBackOverrideType; }
            set
            {
                _convertBackOverrideType = value;
            }
        }

        /// <summary>
        /// Derived classes implement the <see cref="IValueConverter.Convert"/> logic in this method.
        /// </summary>
        protected abstract object InstanceConvert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture);

        /// <summary>
        /// Derived classes implement the <see cref="IValueConverter.ConvertBack"/> logic in this method.
        /// </summary>
        protected abstract object InstanceConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture);

        /// <summary>
        /// Implementation of <see cref="IValueConverter.Convert"/>.  First calls the chained converter if the <see cref="Link"/> property is set before processing the value.
        /// </summary>
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            object retVal = null;


            if (_linkedConverter != null)
            {   // Linked converter is set, this is not the last in the chain

                // call the linked converter, i.e. the next in the chain
                retVal = _linkedConverter.Convert(value, this.OverrideType ?? targetType, parameter, culture);

                // pass the return value from the call above to the implementation of the overriding class.
                retVal = InstanceConvert(retVal, targetType, parameter, culture); 
            }
            else
            {
                // at the end (or beginning, depending on how you look at it) of the chain, or this could be an instance that is not linked,
                // convert it in the overriding class
                retVal = InstanceConvert(value, targetType, parameter, culture); 
            }
            
            // return the final conveted value
            return retVal;
        }

        /// <summary>
        /// Implementation of <see cref="IValueConverter.ConvertBack"/>.  First calls the chained converter if the <see cref="Link"/> property is set before processing the value.
        /// </summary>
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            // this is similar logic of the Convert method, only this works for converting the value back in two way binding

            object retVal = null;
            if (_linkedConverter != null)
            {                
                // NOTE:  The linked converters will be called in reverse order for the ConvertBack logic.
                retVal = InstanceConvertBack(retVal, this.BackOverrideType ?? targetType, parameter, culture);
                retVal = _linkedConverter.ConvertBack(value, targetType, parameter, culture);
            }
            else
            {
                retVal = InstanceConvertBack(value, targetType, parameter, culture);
            }

            return retVal;
        }
    }

The following XAML snippet shows how converters deriving from LinkedConverter can be used.

            <code:NullableValueConverter x:Key="cnvNullable" />
            
            <code:BoolToVisibilityConverter TrueValue="Visible" FalseValue="Collapsed" x:Key="nullToVisibility" >
                <code:BoolToVisibilityConverter.Link>
                    <code:NullToBoolConverter Link="{StaticResource cnvNullable}" />
                </code:BoolToVisibilityConverter.Link>
            </code:BoolToVisibilityConverter>

The figure below is a sample project using these linked converters. They are used for the Age Group labels.

Linked Converters For Age Group

Sample project: LinkedConverters.

Download PDF
This entry was posted in MVVM, Silverlight 4, WPF and tagged , . Bookmark the permalink.

Leave a Reply

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