Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Bindable Converter, Converter Parameter and StringFormat

0.00/5 (No votes)
18 Sep 2012 1  
How to work around that Binding's Converter, ConvertParameter and StringFormat cannot be specified as dynamic Bindings.

Introduction

In this article we develop ways to overcome the limitation that ConverterParameter cannot be specified as dynamic Binding in WPF and Silverlight 5. These solutions are alternatives to the ones described in the article Bindingable Converter Parameter, and other blogs and fora. Features of the solutions presented here are:

  • Not much extra code in helpers
  • Existing converter implementing IValueConverter can be used without modification
  • Works in data templates
  • Supports RelativeSource  
  • No limitation of number of bindings using bindable ConvertParameter per element.
  • Two-way binding supported
  • Support also for binding to Converter, not just the ConverterParameter.
  • Support for binding to StringFormat (NEW IN 2.0)
  • Silverlight 5 support (see last section)

Background

For Bindings in WPF and Windows, converters can be used to customize the appearance. To pass extra information to a converter the ConverterParameter can be used. However, neither the Converter, nor the ConverterParameter can be specified as a Binding, which several developers finds to be a limitation. Some blogs and forum topics recommend to create a converter that derives from DependencyObject. Then, the converter itself can have dependency properties that can be assigned to bindings in XAML. However, this does not work in dynamic contexts and in data templates.

As pointed out in few places, MultiBinding can be a good choice in many cases. In this article we start in this end and develop a few helpers to make it even easier.

Step 1: Use MultiBinding in WPF

As described in some blogs and forums we should first consider to simply use a MultiBinding instead, specifying the converter parameter binding as the second one like this:

<TextBox>
  <TextBox.Text>
     <MultiBinding Converter="{StaticResource WrappedTestValueConverter}" >
        <Binding Path="FirstName" />
        <Binding Path="ConcatSign" />
     </MultiBinding>
  </TextBox.Text>
</TextBox>

In this case we also have to change the Converter to implement the IMultiValueConverter interface. This is simplest done by adding an IMultiValueConverter implementation like this to the original converter:

#region IMultiValueConverter Members

public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
  return Convert(values[0], targetType, values[1], culture);
}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
   return new object[] { ConvertBack(value, targetTypes[0], parameter, culture) };
}

#endregion

The above code calls the original IValueConverter's Convert and ConvertBack methods.

If you do not want or are unable to change the original converter you can use a generic "adapter" IMultiValueConverter that simply delegates conversion to the original IValueConverter implementation. A generic implementation of such adapter is shown below:

[ContentProperty("Converter")]
public class MultiValueConverterAdapter : IMultiValueConverter
{
    public IValueConverter Converter { get; set; }

    private object lastParameter;

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (Converter == null) return values[0]; // Required for VS design-time
        if (values.Length > 1) lastParameter = values[1]; 
        return Converter.Convert(values[0], targetType, lastParameter, culture);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        if (Converter == null) return new object[] { value }; // Required for VS design-time

        return new object[] { Converter.ConvertBack(value, targetTypes[0], lastParameter, culture) };
    }
}

To use the adapter you can simply wrap the original IValueConverter instance declaration in the resource section like this:

<local:MultiValueConverterAdapter x:Key="WrappedTestValueConverter">
 <local:TestValueConverter />
</local:MultiValueConverterAdapter>

With this solution we do not have to use any custom bindings in the XAML code, just standard MultiBinding which works also in templates and styles. All required changes are performed on the converter side. With the use of the generic adapter we do not alter the original converters at all.

A limitation with this approach is that it does not fully support two-way binding. The problem is that the parameter passed in as the second value to the Convert method will not be available when ConvertBack is called. This is not be a problem if the parameter is not used in the back-conversion. To partly circumvent this problem the parameter from Convert can be stored and reused in ConvertBack as shown in the MultiValueConverterAdapter implementation above. However, this does not work in scenarios in which the same converter instance is used for several XAML elements, for instance in a data templated list. For a solution, please read on.

Step 2: A new markup extension

To solve the problem with shared converter instances causing problems in a two-way scenario, we can create a new simple markup extension. In this way we also get a more understandable XAML code and do the XAML author does not have to wrap the value converter resource declaration as shown above.

The full implementation of the custom markup extension is shown below:

public class ConverterBindableBinding : MarkupExtension
{
  public Binding Binding { get; set; }
  public IValueConverter Converter { get; set; }
  public Binding ConverterParameterBinding { get; set; }

  public override object ProvideValue(IServiceProvider serviceProvider)
  {
     MultiBinding multiBinding = new MultiBinding();
     multiBinding.Bindings.Add(Binding);
     multiBinding.Bindings.Add(ConverterParameterBinding);
     MultiValueConverterAdapter adapter = new MultiValueConverterAdapter();
     adapter.Converter = Converter;
     multiBinding.Converter = adapter; 
     return multiBinding.ProvideValue(serviceProvider);
   }
}

For markup extensions, the ProvideValue is called whenever the extension is to be applied to an XAML element. What we simply do in this method is to programmatically create a new MultiBinding with the original binding and the converter parameter as sub-bindings, and a new fresh MultiValueConverterAdapter instance. As you see this is very similar to what we did in XAML above, but with the difference that we get a new instance of the adapter for each time the binding is used.

We can now replace the MultiBinding XAML code above with this markup extension like this:

<TextBox>
  <TextBox.Text>
    <local:ConverterBindableBinding Binding="{Binding FirstName}"
                                       Converter="{StaticResource TestValueConverter}" 
                                       ConverterParameterBinding="{Binding ConcatSign}" />
  </TextBox.Text>
</TextBox>

As you see we can use the original TestValueConverter without any changes to its source code or the resource definition. Furthermore, with this solution it is much easier to understand that the ConcatSign binding is used as a source for the ConverterParameter, compared to the MultiBinding solution above. So even if you do not have problems with two-way bindings you likely want to use this solution.

There is still a limitation when using this markup extension in a Style context, because then the same instances of MultiBinding and MultiValueConverterAdapter, created in the ProvideValue method, will be used in all places where the style is applied. Therefore, do not use it in a style if you need two-way binding that uses the converter parameter in the ConvertBack implementation.

Bindable Converter

The solution above can now easily be extended to support binding to the Converter also, not just the ConverterParameter. We just add a new property, ConverterBinding, for that in ConverterBindableBinding and pass this into the MultiConverterValueAdapter as a third binding. See submitted source code for the details.

Support for StringFormat (NEW in 2.0)

In the new version (2.0) I have added support for StringFormat too, You can even have a dynamic string format using the StringFormatBinding property. For this I created a new BindableBinding class that is used like this:

<TextBox>

  <TextBox.Text>

     <local:BindableBinding Path="FirstName"

                            Converter="{StaticResource TestValueConverter}"

                            ConverterParameterBinding="{Binding ConcatSign}"

                            StringFormatBinding="{Binding CurrentStringFormat}"/>

   </TextBox.Text>

</TextBox>

A Silverlight 5 solution

The above solutions work for WPF. For Silverlight 5 you can use the solution presented in my article "MultiBinding in Silverlight 5". This MultiBinding implementation supports bindable ConverterParameter and Converter out-of-the box. In contrast to the WPF MultiBinding, it also directly supports the use of IValueConverter, so no adapter converter is needed. In this case the XAML code will be something like this:

<TextBox Text="{z:MultiBinding Converter={StaticResource TestValueConverter} 
               ConverterParameter={Binding ConcatSign} Binding1={Binding FirstName} }" /> 

This Silverlight solution can be used in a Style as well, without problems with shared resources when doing two-way binding.

History

  • September 17, 2012 - First version submitted.
  • September 19, 2014 - Version 2.0 with support for StringFormat submitted.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here