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

Bindable Converter Parameter

0.00/5 (No votes)
2 Jul 2013 5  
A simple technique to achieve Bindable-ConverterParameter in WPF's XAML.

 

Introduction

Binding is one of the most powerful features introduced in WPF, it allows us to implement complex data-based UI, using minimum of declarative code (XAML), while relying on a 'built-in', out of the box mechanism that connects data to UI-controls in a direct way or through Converters.

Binding converters are by nature an encapsulated functions that receive the bound value, manipulate it in a specific manner, and return the manipulated result. On many occasions another source of information is needed for the converter in order to 'do its job', this source is called 'ConverterParameter' and it's one of the converter's Convert and ConvertBack method parameters.

WPF's out-of-the-box Bindings restricts the ConverterParameter to receive (from XAML) only Static or Resource values, while passing it a Binding expression is forbidden:

//
// this line of xaml produce the following error:
//
<TextBox Text="{Binding Path=FirstName,Converter={StaticResource TestValueConverter},ConverterParameter={Binding Path=ConcatSign}}" />

A 'Binding' cannot be set on the 'ConverterParameter' property of type 'Binding'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.

In many cases this feature (Bindable ConverterParameter) is exactly what ones need. I.e., the ConverterParameter value which is a Binding-Expression (rather than a static predefined value) will be evaluated each time the binding evaluation is triggered, and will passed to the Converter as the ConverterParameter parameter.

In this article I'll show a relatively simple and straightforward technique to achieve just that. The XAML part that will implement the bound ConverterParameter will look as close as possible to the Error producing line of code above, with the exception that it will work... 

Bindable Converter Parameter Take-3  

Updates

  • 29/6/13
    - Added feature : Element-Name Binding 
    - Bug Fix : Memory-Leak 

This is my Third attempt in achieving a robust, simple solution for this all to well known handicap. 

Main Goal

Forging The Concepts described in the previous attemts into Real-World, Robust solution

Supported Features & Virtues:

  1. Bindable Converter Parameter/s (Also a mix of Bindings, Static-values, Enums*)
    *
    there are better ways to implement Enum-Based-Binding, The addition of ConverterParameter's Enum support to BcpBinding Markup-Extension class, is ment for demonstrating its ability to handle new, different, kinds of types, while applying only minor code modifications.
  2. Multi-Binding support
  3. Two-Way Binding support
  4. Readable comprehensible XAML :
    <!--THIS SAMPLE SHOWS SINGLE-BINDING WITH BINDED/MIXED ConverterParameters SYNTAX-->
    <TextBox >
    
        <TextBox.Text >
            <local:BcpBinding Path="FirstName" 
               ConverterParameters="Binding Path=ConcatSign,Binding Path=ConcatSign2" 
               Converter="{StaticResource TestValueConverterWithTwoObjectsParameter}" 
               Mode="TwoWay"/>
        </TextBox.Text>
        <TextBox.Tag >
            <local:BcpBinding Path="FirstName" 
              ConverterParameters="Binding Path=ConcatSign,Binding Path=ConcatSign2,[some not binded value]" 
              Converter="{StaticResource TestValueConverterWithThreeObjectsParameter}" />
        </TextBox.Tag>
    </TextBox>
    <!--THIS SAMPLE SHOWS MULTI-BINDING WITH BINDED ConverterParameters SYNTAX-->
    <TextBox >
        <TextBox.Text >
            <local:BcpBinding 
                   Converter="{StaticResource MultiValueMultiConverterParameterConverter}" 
                   Mode="TwoWay" >
                Binding Path=FirstName
                Binding Path=LastName
                <local:BcpBinding.ConverterParameters>
    
                    Binding Path=ConcatSign
                    Binding Path=ConcatSign2
                    Binding Path=AllConcatSigns
                </local:BcpBinding.ConverterParameters>
            </local:BcpBinding>
        </TextBox.Text>
    </TextBox>
    
    <!--THIS SAMPLE SHOWS MULTI-BINDING WITH MIXED ConverterParameters SYNTAX-->
    <TextBox >
    
        <TextBox.Text >
            <local:BcpBinding Converter="{StaticResource MultiValueMultiMixedConverterParameterConverter}" 
                  Mode="TwoWay" 
                  ConverterParameters="Binding Path=ConcatSign,Binding 
                    Path=ConcatSign2,[some not binded value],Binding Path=AllConcatSigns">
                Binding Path=FirstName
                Binding Path=LastName
            </local:BcpBinding>
        </TextBox.Text>
    </TextBox>
  5. Binded ConverterParameter change DOES NOT trigger reevaluation of the Binding(including converter):
    In my humble opinion, Binded ConverterParameter change Shold not trigger the binding it is a member of.
    For if we had wanted this behevior we'd placed it as another Binding inside an, already existing, out-of the-box, MultiBinding.
    As i see it, ConverterParameter is just an extara data the Converter needs in order to perform it's job (whether it is a static value, or a value evalueted from a binding expression)
    The only scenario i know of, on which we would want a binding to trigger a 'Parent-Binding' & also come as a ConverterParameter is on a MultiValueConverter ConvertBack case. here, we are expected to extract multiple values out of a single value and might need the original values, the ConvertBack's value, is made of.
    On such a case i would use asyntax like this:
    <TextBox >
        <TextBox.Text >
            <local:BcpBinding Converter="{StaticResource some-converter}" Mode="TwoWay" 
                ConverterParameters="Binding Path=FirstName,
                  Binding Path=LastName">
    
                Binding Path=FirstName
                Binding Path=LastName 
            </local:BcpBinding>
        </TextBox.Text>
    </TextBox>  
  6. Binding to Indexers (i.e. SomeIndexer[]) support. Both in Bindings & ConverterParameter-Bindings.

Comments:

  • As this solution is based on the previous ones , i've omitted explanations in this section(TAKE3). I've placed Comments inside the code instead.
  • Binding syntax is weakly-typed as Bindings & ConverterParameters are passed as strings to the Markup-Extenssion.
  • I have Omitted Error-Validations in the code ,as it adds extra code which obscures the relevant code that implements the idea of the solution.

#End of TAKE3 

Bindable Converter Parameter Take-2 

This is my second attempt in achieving a robust, simple solution for this all to well known handicap. 

Main Goals: 

  1. XAML code that will resemble the intended code as possible.
  2. Simple & short.
  3. Minimal effect(Impact) over existing code (both in xaml & overall project). 
  4. Support for unlimited  number of Bindable-ConverterParameter bindings per element. 
  5. Support for Two-Way-Binding (for the  Binding that has the Bindable ConverterParameter). 

Solution Principles

  1. Let user write XAML in a form that is as close as possible to the desired (unavailable form). I chose this:
  2. <TextBox Text="{Binding Path=FirstName,Converter={StaticResource 
      TestValueConverter},ConverterParameter='Binding Path=ConcatSign',Mode=TwoWay}" />
  3. Create an Outside-Mechanism that will stitch things up 'Behind the scenes' in order to achieve the desired functionality.

The solution itself:

  1. Write XAML as you would under normal circumstances, when ever you need to apply 'Bindable ConverterParameter' write it as shown in the above code.
  2. For the Element, that has in It's scope those special kind of bindings, Register for the_Initialized event in code-behind (for those who are MVVM fanatics i.e. discards the use of code-behind under any circumstances, or when there is no code behind using Behavior is advised) 
  3. In the _Initialized event handler do the following:
  • Get all DependencyObjects under the Initialized-Element scope.
  • For each DependencyObject - Get All Dependecy-Properties that has this  special form of Binding syntax.
  • For each DependecyProperty do the following (see comments in code):
  • // 1. get original binding 
    Binding bindingOrig = BindingOperations.GetBinding(item, dp);
    
    //create set of attached properties to replase this ConverterParameter Binding 
    //give unique name by using dp's name:
    DependencyProperty apBindingSource=null;
    
    //(5.) another attached prop for two-way binding operations
    DependencyProperty apIsSourceChanged = 
       DependencyProperty.RegisterAttached(dp.Name + "IsSourceChanged", typeof(bool), 
       item.GetType(), new PropertyMetadata(false));
       
    // 1. attached-prop for the ConverterParameter-Binding
    
    DependencyProperty apConverterParameterBindingSource = 
       DependencyProperty.RegisterAttached(dp.Name + "ConverterParameterBindingSource", 
       typeof(object), item.GetType(), new PropertyMetadata(null)); 
    Binding bindingConverterParameterBindingSource = new Binding(sConverterParameterBindingPath);
    BindingOperations.SetBinding(item, apConverterParameterBindingSource, 
                                 bindingConverterParameterBindingSource);
    
    // 2. attached-prop to hold the Converter Object
    DependencyProperty apConverter = DependencyProperty.RegisterAttached(dp.Name + 
      "Converter", typeof(IValueConverter), item.GetType(), new PropertyMetadata(null));
    (item).SetValue(apConverter, bindingOrig.Converter);
    
    //3. attached-prop to hold the evaluate result - will be binded to the original Binded dp
    
    DependencyProperty apEvaluatedResult = DependencyProperty.RegisterAttached(dp.Name + 
      "EvaluatedResult", typeof(object), item.GetType(), new PropertyMetadata(null, (s, edp) =>
    {
        if (!(bool)s.GetValue(apIsSourceChanged))
        {
            // change didn't come from source - target got changed in two-way binding
            // change source via convert back
            object ret= (s.GetValue(apConverter) as IValueConverter).
            ConvertBack(edp.NewValue, null, s.GetValue(apConverterParameterBindingSource), null);
            s.SetValue(apBindingSource, ret);
        }
    }));
    
    // 4. attached-prop to replace the source binding - bind apBindingSource
    // to the original source (instead of the original dp)
    apBindingSource = DependencyProperty.RegisterAttached(dp.Name + 
      "BindingSource", typeof(object), item.GetType(), new PropertyMetadata(null, (s, edp) =>
    {
      s.SetValue(apIsSourceChanged, true);
      s.SetValue(apEvaluatedResult, (s.GetValue(apConverter) as IValueConverter).Convert(
       edp.NewValue, null, s.GetValue(apConverterParameterBindingSource), null));
      s.SetValue(apIsSourceChanged, false);
    }));
    Binding NewBindingToSource = new Binding(bindingOrig.Path.Path); 
    NewBindingToSource.Mode =bindingOrig.Mode;
    // reroute source to apBindingSource
    BindingOperations.SetBinding(item, apBindingSource, NewBindingToSource);

Basically what we are doing is to replace the 'special' binding with a set of uniquely named attached properties. Then reroute the original bindings route through those attached properties. The following sketch will try to clear this up(ap=AttachedProperty):

This:

TARGET <---Binding---> SOURCE using (CONVERTER-PARAMETER <---Binding---> CONVERTER-PARAMETER SOURCE) via CONVERTER

Becomes:

TARGET <---Binding---> Result_ap <---get updated-- (Souce_ap <---Binding---> SOURCE) using (ConverterParameter_ap <---Binding---> CONVERTER-PARAMETER SOURCE ) via Converter_ap 

#End of TAKE2 

A Note

The following parts will describe my solution for the problem at hand. As this solution is fairly simple, I've chose not to make it more complex than it should. That is why, for example, I chose to use a pre-defined set of bindings in the Multi-Binding solution rather than add an elaborated mechanism that will obscure the main ideas I'm hoping to convey. This solution is a variant of a technique I've already shown in my article 'Silverlight Multi-Binding' with some WPF and context specific modifications and tweaks.

Using the code

Known facts

  • Bindings 'naturally' 'lives' inside the framework, where they re-evaluate themselves in response to the relevant PropertyChanged events.
  • Attached properties can have binding.
  • Attached Properties are subset of Dependency Properties, hence they have a Property-Changed-handling built-in mechanism.
  • Custom Markup Extension can mimic (emulate) other types of markup extensions known as 'Binding'.

Ingredients

For this solution we'll need the following classes:

  • CustomBindingUtil.cs: This class will hold a set of attached properties that will be attached to the Framework-element we wish to have the binding. Also, it will perform the actual value-production in response to the Binding/s changes. I've divided this class into three regions: Single-Binding, Multi-Binding, Shared.
  • #region Single Binding Attached-Properties
    
    public static object GetSingleBinding(DependencyObject obj)
    {
        return (object)obj.GetValue(SingleBindingProperty);
    }
    
    public static void SetSingleBinding(DependencyObject obj, object value)
    {
        obj.SetValue(SingleBindingProperty, value);
    }
    
    // Using a DependencyProperty as the backing store for SingleBinding.
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SingleBindingProperty =
        DependencyProperty.RegisterAttached("SingleBinding", typeof(object), 
        typeof(CustomBindingUtil), new PropertyMetadata(null, SingleBindingChanged));
    
    private static void SingleBindingChanged(DependencyObject obj, 
            DependencyPropertyChangedEventArgs e)
    {
        try
        {
            object convparam = obj.GetValue(ConverterParameterProperty);
            object binding = obj.GetValue(SingleBindingProperty);
            obj.SetValue(BResultProperty, (obj.GetValue(ConverterProperty) 
                as IValueConverter).Convert(binding, null, convparam, null));
        }
        catch (Exception ex)
        {
            throw;
        }
    }
    
    public static object GetBResult(DependencyObject obj)
    {
        return (object)obj.GetValue(BResultProperty);
    }
    
    public static void SetBResult(DependencyObject obj, object value)
    {
        obj.SetValue(BResultProperty, value);
    }
    
    // Using a DependencyProperty as the backing store for BResult.
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty BResultProperty =
        DependencyProperty.RegisterAttached("BResult", typeof(object), 
        typeof(CustomBindingUtil), new UIPropertyMetadata(null));
    
    
    #endregion
    
    #region Multi Binding Attached-Properties
    #region Predefined Bindings
    
    
    public static Object GetBinding1(DependencyObject obj)
    {
        return (Object)obj.GetValue(Binding1Property);
    }
    public static void SetBinding1(DependencyObject obj, Object value)
    {
        obj.SetValue(Binding1Property, value);
    }
    // Using a DependencyProperty as the backing store for Binding1.
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty Binding1Property =
        DependencyProperty.RegisterAttached("Binding1", typeof(Object), 
        typeof(CustomBindingUtil), new PropertyMetadata(null, BindingChanged));
    
    public static Object GetBinding2(DependencyObject obj)
    {
        return (Object)obj.GetValue(Binding2Property);
    }
    public static void SetBinding2(DependencyObject obj, Object value)
    {
        obj.SetValue(Binding2Property, value);
    }
    // Using a DependencyProperty as the backing store for Binding2.
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty Binding2Property =
        DependencyProperty.RegisterAttached("Binding2", typeof(Object), 
        typeof(CustomBindingUtil), new PropertyMetadata(null, BindingChanged));
    
    
    public static Object GetBinding3(DependencyObject obj)
    {
        return (Object)obj.GetValue(Binding3Property);
    }
    public static void SetBinding3(DependencyObject obj, Object value)
    {
        obj.SetValue(Binding3Property, value);
    }
    // Using a DependencyProperty as the backing store for Binding3.
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty Binding3Property =
        DependencyProperty.RegisterAttached("Binding3", typeof(Object), 
        typeof(CustomBindingUtil), new PropertyMetadata(null, BindingChanged));
    
    #endregion
    
    /// <summary>
    /// update result property with converted (& Weighted) all available sub-binding results
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="e"></param>
    
    private static void BindingChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        object[] Values = new Object[] { obj.GetValue(Binding1Property), 
                 obj.GetValue(Binding2Property), obj.GetValue(Binding3Property) };
        try
        {
            object convparam = obj.GetValue(ConverterParameterProperty);
            obj.SetValue(MBResultProperty, (obj.GetValue(MultiConverterProperty) 
                as IMultiValueConverter).Convert(Values, null, convparam, null));
        }
        catch (Exception ex)
        {
            throw;
        }
    }
    
    public static IMultiValueConverter GetMultiConverter(DependencyObject obj)
    {
        return (IMultiValueConverter)obj.GetValue(MultiConverterProperty);
    }
    
    public static void SetMultiConverter(DependencyObject obj, IMultiValueConverter value)
    {
        obj.SetValue(MultiConverterProperty, value);
    }
    
    // Using a DependencyProperty as the backing store for MultiConverter. 
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty MultiConverterProperty =
        DependencyProperty.RegisterAttached("MultiConverter", 
        typeof(IMultiValueConverter), typeof(CustomBindingUtil), new UIPropertyMetadata(null));
    
    public static Object GetMBResult(DependencyObject obj)
    {
        return (Object)obj.GetValue(MBResultProperty);
    }
    public static void SetMBResult(DependencyObject obj, Object value)
    {
        obj.SetValue(MBResultProperty, value);
    }
    // Using a DependencyProperty as the backing store for MBResult.
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty MBResultProperty =
        DependencyProperty.RegisterAttached("MBResult", typeof(Object), 
        typeof(CustomBindingUtil), new PropertyMetadata(null));
    
    #endregion
    
    #region Shared Attached-Properties
    
    public static IValueConverter GetConverter(DependencyObject obj)
    {
        return (IValueConverter)obj.GetValue(ConverterProperty);
    }
    public static void SetConverter(DependencyObject obj, IValueConverter value)
    {
        obj.SetValue(ConverterProperty, value);
    }
    // Using a DependencyProperty as the backing store for Converter.
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ConverterProperty =
        DependencyProperty.RegisterAttached("Converter", 
        typeof(IValueConverter), typeof(CustomBindingUtil), new UIPropertyMetadata(null));
    
    public static object GetConverterParameter(DependencyObject obj)
    {
        return (object)obj.GetValue(ConverterParameterProperty);
    }
    
    public static void SetConverterParameter(DependencyObject obj, object value)
    {
        obj.SetValue(ConverterParameterProperty, value);
    }
    
    // Using a DependencyProperty as the backing store for ConverterParameter.
    // This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ConverterParameterProperty =
        DependencyProperty.RegisterAttached("ConverterParameter", 
        typeof(object), typeof(CustomBindingUtil), new PropertyMetadata(null));
    
    #endregion
  • MyBindedParameterBinding.cs - This is a custom Markup-Extension class. Its main goal is to emulate the known {Binding...} Markup-Extension, only with the added value of the Bindable ConverterParameter feature. For doing that it has three matching properties : Binding (of type Binding), Converter (of type IValueConverter), and ConverterParameter (of type Binding) in the Markup-Extension's ProvideValue override method:
  • Bindings (and property) are set to the element's matching attached properties, and the return value is set to a new Binding's (referencing the element's CustomBindingUtil.BResultProperty Attached property) - the ProvideValue method.

    public Binding Binding { get; set; }
    public IValueConverter Converter { get; set; }
    public Binding ConverterParameter { get; set; }
    
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        try
        {
            IProvideValueTarget pvt = serviceProvider.GetService(
              typeof(IProvideValueTarget)) as IProvideValueTarget;
            DependencyObject TargetObject = pvt.TargetObject as DependencyObject;
    
            BindingOperations.SetBinding(TargetObject, 
              CustomBindingUtil.SingleBindingProperty, Binding);
            BindingOperations.SetBinding(TargetObject, 
              CustomBindingUtil.ConverterParameterProperty, ConverterParameter);
            TargetObject.SetValue(CustomBindingUtil.ConverterProperty, this.Converter);
            Binding b = new Binding();
            b.Source = TargetObject;
            b.Path = new PropertyPath(CustomBindingUtil.BResultProperty);
            return b.ProvideValue(serviceProvider);
        }
        catch (Exception)
        {
    
            throw;
        }
    }
  • MyBindedParameterMultiBinding.cs - the same principles as in MyBindedParameterBinding.cs with additional tweaks in order to support MultiBinding. A better (more elegant) way to handle multiple bindings is advice- see above note.

The mechanism (for single binding)

Starting in the XAML:

The code is fairly close to the known binding syntax:

<TextBox >
    <TextBox.Text >
        <local:MyBindedParameterBinding Converter="{StaticResource TestValueConverter}" 
           Binding="{Binding Path=FirstName}" 
           ConverterParameter="{Binding Path=ConcatSign}"/>

    </TextBox.Text>
</TextBox>

When the FirstName property get changed, the element's Binding AttachedProperty gets changed (as it is bound to FirstName). Which triggers the SingleBindingChanged method (inside CustomBindingUtil.cs), there BResultProperty(another Attached property) is set to the evaluated value (the result of the value, passed into the Converter's Convert method along with a currently evaluated ConverterParameter parameter).

private static void SingleBindingChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    try
    {
        object convparam = obj.GetValue(ConverterParameterProperty);
        object binding = obj.GetValue(SingleBindingProperty);
        obj.SetValue(BResultProperty, (obj.GetValue(ConverterProperty) 
          as IValueConverter).Convert(binding, null, convparam, null));
    }
    catch (Exception ex)
    {
        throw;
    }
}

The BResultProperty is what our custom Markup-Extension is returning via binding.

public override object ProvideValue(IServiceProvider serviceProvider)
        ...

        Binding b = new Binding();
        b.Source = TargetObject;
        b.Path = new PropertyPath(CustomBindingUtil.BResultProperty);
        return b.ProvideValue(serviceProvider);
    }
    catch (Exception)
    {

        throw;
    }
}

In Conclusion

The technique described here can be utilized by any one who faces the need for a Bindable ConverterParameter. Just add CustomBindingUtil.cs and MyBindedParameterBinding.cs or/and MyBindedParameterMultiBinding.cs to your project, and you are done. The example shown here is not intended to be a robust, production-oriented code-product as it has many loose ends (such as when trying to use more than one Bindable ConverterParameter), nevertheless it can by broadened to become one.

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