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]; 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 };
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 MultiConverte
rValueAdapter
as a third binding. See submitted source code for the details.
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.