Friday, June 18, 2021

Adding a converter parameter to a custom binding markup extension

My earlier blog entry about a markup extension to simplify binding in an Infragistics TemplateField is good as far as it goes, but I wanted to add the ability to specify a Converter. This turned out to be far more complex than I thought it would be.

I thought it would be a matter of specifying a Converter property and adding it to the Binding. But when you add a StaticResource to the parameter list of a markup extension, it causes the sequence of events to change and stuff gets evaluated before you are ready. I want to thank this codeproject article for explaining it so well.

Let's take the earlier project and modify it the way I thought it would work. Open the XamDataGridTemplateField solution from my earlier entry. Add a new class called Converters and add a trivial converter to it like this.

using System;
using System.Globalization;
using System.Windows.Data;
 
namespace XamDataGridTemplatedField
{
    class FontSizeConverter :IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
 
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
    }
}

Now open the TemplateBindings class and make these changes.

  • Replace the Path member with a property
  • Add a Converter property of type IValueConverter
  • Add a default constructor
        public String Path { get; set; }
        public IValueConverter Converter { get; set; }
 
        public TemplateBinding()
        { }

  • In CreateBinding, assign the converter.

            b.Converter = Converter;

Open MainWindow.xaml and make these changes... 

Add a resources section referencing the new converter

    <Window.Resources>
        <local:FontSizeConverter x:Key="FontSizeConverter"/>
    </Window.Resources>

and modify the FontSize binding in the two TextBoxes.

<TextBlock Text="{igEditors:TemplateEditorValueBinding}" FontSize="{local:TemplateBinding Path=fontSize, Converter={StaticResource FontSizeConverter}}"/>

If you run the application now you will get an error.


If you debug you will see that referring to the StaticResource in the binding causes InitializeComponent to get called earlier than before which ends up raising this exception. The article in CodeProject explains how to create a "singleton" version of the converter that can be referenced directly in the binding. Let's do that now.

Change the converter to make it inherit from MarkupExtension, defer creation of the object until ProvideValue, and implement a limited singleton pattern. The important thing is that we are making the converter a Markup Extension in its own right so we can reference it directly. I don't know why this is not standard practice.

using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;
 
namespace XamDataGridTemplatedField
{
    class FontSizeConverter : MarkupExtension, IValueConverter
    {
        private static FontSizeConverter _converter;
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null)
                return null;
            else
                return
value;
        }
 
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
 
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (_converter == null)
            {
                _converter = new FontSizeConverter();
            }
            return _converter;
        }
    }
}
 
Now open MainWindow.xaml, remove the StaticResource and alter the FontSize bindings to reference the converter directly.

<TextBlock Text="{igEditors:TemplateEditorValueBinding}" FontSize="{local:TemplateBinding Path=fontSize, Converter={local:FontSizeConverter}}"/>

If you run the application now you will see the binding was successful. Put a break point in the converter's Convert method to see that it is being called.


 Note: We added the extra constructor so we could bind with two different syntaxes.

<TextBlock Text="{igEditors:TemplateEditorValueBinding}" FontSize="{local:TemplateBinding Path=fontSize, Converter={local:FontSizeConverter}}"/>

<TextBlock Text="{igEditors:TemplateEditorValueBinding}" FontSize="{local:TemplateBinding fontSize, Converter={local:FontSizeConverter}}"/>


No comments:

Post a Comment