Implementing a Substring Converter for use in XAML Bindings

This post was written by Ben Clark.

Whilst creating a label template for a Telerik RadChartView chart I came across the interesting problem of needing to bind to a field in XAML and for display purposes return a substring of the binding. Normally the place to do this would be to perform the substring in the controls View Model, however for this example the binding itself was not available.

<DataTemplate>
       <TextBlock Text="{Binding}" FontWeight="ExtraBold" Foreground="Red"/>
</DataTemplate>

No access to the binding in code-behind, so how do we substring?

The answer was to create a specialist Converter that allowed as the parameter the parameter(s) that SubString() takes.

For example:

<TextBlock Text="{Binding, Converter={StaticResource StringSubstringConverter}, ConverterParameter='5'}" />

Will return the binding (assuming it's a string) with SubString(5), in this case returning the 5th character onwards.

As well as startIndex, length is also supported:

<TextBlock Text="{Binding, Converter={StaticResource StringSubstringConverter}, ConverterParameter='5,5'}" />

Will return the binding (again, assuming it's a string) with SubString(5), in this case returning 5 characters of the string starting at the 5th character.

Simple so far, but we were looking to remove some characters from the end of the string. Normally we'd use as the length parameter the string length minus however many characters we wanted to remove, but how do we know in XAML the length of the binding?

The solution comes by implementing a similar notation to that of the PHP substr function. This allows for negative values to be used to indicate counting from the end of the string. I implemented negative numbers in the length parameter to indicate lengths relative to the end of the string. (PHP substr also supports negative numbers in the startIndex, however this has not yet been implemented).

The final output, to take a string binding and return the string minus the final character is as follows:

<TextBlock Text="{Binding Converter={StaticResource StringSubstringConverter}, ConverterParameter='0,-1'}" />

Success!

Below is the code for the converter, it's not guaranteed bug free, nor the most efficient implementation, but it should work.

using System;

using System.Globalization;
using System.Windows.Data;

namespace WePredict.ConvertersDemo
{
    public class StringSubstringConverter : IValueConverter
    {
        #region IValueConverter Members


        /*
         * Return a substring of the input string using the same notation as SubString()
         *
         * Parameter startIndex | startIndex,length
         *
         * To obtain the functionality of length = (string.length - x) for the length, provide a negative length value
         */

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            //Potential Substring parameters
            int startIndex;
            int length;

            //Input as String
            var val = (string) value;

            //Attempt to split parameters by comma
            string[] parameters = ((string) parameter).Split(',');

            //invalid or no parameters, return full string
            if (parameters == null || parameters.Length == 0)
                return val;

            //return remaining string after startIndex
            if (parameters.Length == 1)
            {
                startIndex = int.Parse(parameters[0]);

                return val.Substring(startIndex);
            }


            //return length characters of string after startIndex
            if (parameters.Length >= 2)
            {
                startIndex = int.Parse(parameters[0]);
                length = int.Parse(parameters[1]);

                if(length >= 0)
                    return val.Substring(startIndex, length);


                //negative length was provided
                return val.Substring(startIndex, val.Length + length);
            }

            return val;
        }

        /*
         * Not Implemented.
         */
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return null;
        }

        #endregion
    }
}