Click here to Skip to main content
Click here to Skip to main content

MultiBinding in Silverlight 5

, 18 Nov 2011
Rate this:
Please Sign up or sign in to vote.
An enhanced MultiBinding markup extension implementation for Silverlight 5 with support for bindable Converter, ConverterParameter and StringFormat
Screenshot from Silverlight Multibinding Demo App

Introduction

This article presents a MultiBinding implementation for Silverlight 5, enabling aggregating values from several sources to one target dependency property. In contrast to WPF, Silverlight only supports single-value Binding out-of-the-box, but thanks to the support for custom markup extensions introduced in Silverlight 5, it was possible to write a MultiBinding implementation with similar syntax and functionality as the WPF version of MultiBinding.

This markup extension MultiBinding supports all types of source bindings including data context sensitive ones and bindings with named elements and RelativeSource. It also supports two-way binding, i.e., converting back from a single target value to multiple source values. It can also be applied to several elements using styling. In some aspects, this Silverlight version extends the WPF MultiBinding:

  1. Source bindings can be declared using XAML markup attribute-syntax ({z:MultiBinding}) as a complement to specifying the source bindings in a collection using the element syntax (<z:MultiBinding>).
  2. The MultiBinding properties Converter, ConverterParameter and StringFormat can be bound dynamically to arbitrary sources using Bindings.
  3. Sources are not restricted to Bindings. String constants, XAML objects, StaticResource and other markup extensions can be used as individual sources.
  4. In two-way binding, the normal Silverlight validation mechanism can be used when exceptions are thrown from the user-provided IMultiValueConverter.ConvertBack implementation.
  5. Converters implementing the single-value IValueConverter can also be used with this MultiBinding, making it useful for single source bindings with Converter, ConverterParameter or StringFormat changing during run-time.

Background

Unlike an ordinary Binding, MultiBinding allows bindings of more than one source to a single dependency property. One common usage is to present a customized text that includes values from several sources. For such scenarios, MultiBinding offers a StringFormat property to define the format with placeholders for the source values to be inserted. But the usage is not restricted to text. A custom IMultiValueConverter can be used to specify how the source values are to be combined.

In WPF, a MultiBinding must be defined in XAML element syntax like this:

<TextBlock>
    <TextBlock.Text>
        <MultiBinding StringFormat="{}{0} {1}" >
            <Binding Path="FirstName" />
            <Binding Path="LastName" />
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

For Silverlight 3 and 4, Colin Eberhardt has presented a solution for MultiBinding based on an attached property (see this link and this link). In comparison, this Silverlight 5 solution presented here supports markup extension syntax, bindable Converter, ConverterParameter and StringFormat properties and fully supports source bindings with ElementName and RelativeSources. Moreover, bindable properties are not restricted to those in the System.Windows.Control namespace. On the other hand, it is not possible to directly use the MultiBinding solution presented below in WPF, mainly because the MultiBinding uses the Silverlight-specific IMarkupValue<T> interface.

At the time of writing this article, ntg123 published another Silverlight MultiBinding solution. See the article Silverlight Multi-Binding for more information.

When using the Model-View-ViewModel pattern (MVVM), MultiBinding is often not essential. Aggregation of several (model) sources can be performed in a view-model property. Care must however be taken to ensure that change notification is done for the aggregated property whenever one of the source values are changed. Although MVVM also results in more unit testable code, sometimes it is not desired or necessary to use this pattern, if the same result can be achieved simpler by bindings in the view or to the model directly. I encourage you to think about alternative solutions for the MultiBinding use case examples shown below, for instance using the MVVM pattern.

Using the Code

Simple Model Binding with Text Formatting

If we have a model or view model object with properties for FirstName and LastName as the data context, we can use the MultiBinding to present the full name in this way:

<TextBlock Text="{z:MultiBinding Source1={Binding FirstName}, 
    Source2={Binding LastName}, StringFormat='%1, %0' }" />    

Whenever FirstName or LastName is modified, the text will be updated provided that the model object supports change notification. MultiBinding supports up to 5 sources to be specified as attributes, so as long as the number of sources is not greater, simple XAML attribute syntax can be used. When the number of sources grows larger or some of the properties cannot be specified in attribute syntax, the XAML element syntax can be used instead as shown in the next example where we use MultiBinding in a list box data template to show full names of Persons. The user can select the name format in a combo box.

<ComboBox Name="cboNameFormat" Width="180" SelectedValuePath="Tag" >
     <ComboBoxItem Tag="%0 %1" IsSelected="True">First and last name</ComboBoxItem>
     <ComboBoxItem Tag="%1, %0">Last name, first name</ComboBoxItem>
</ComboBox>

<ListBox ItemsSource="{Binding Persons}" >
   <ListBox.ItemTemplate>
      <DataTemplate>
        <TextBlock>
           <TextBlock.Text>
              <local:MultiBinding StringFormat=
        "{Binding SelectedValue, ElementName=cboNameFormat}" >
                <local:BindingCollection>
                   <Binding Path="FirstName" />
                   <Binding Path="LastName" />
                </local:BindingCollection>
            </local:MultiBinding>
          </TextBlock.Text>
        </TextBlock>
     </DataTemplate>
   </ListBox.ItemTemplate>
</ListBox>

As demonstrated above, this MultiBinding supports specifying the value placeholders in StringFormat with %n instead of the ordinary {n} syntax, since the latter requires a lot of escaping when used in XAML. Another important difference compared to the WPF version is that StringFormat can be specified as a binding as well. In the snippet above, we bind StringFormat directly to the combobox selected value. Changing selection will affect the formatting of all names in the listbox, as shown in the screenshot at the top of the article.

In contrast to the WPF version StringFormat is always applied regardless of the destination property type. The original WPF MultiBinding only applies StringFormat when the target property is of String type, which means that it is not useful directly in a ContentControl derived content.

Using a Custom IMultiValueConverter

Besides simple string formatting, the Silverlight MultiBinding allows you to provide a custom converter which is responsible for converting the source values to one single value to be used. The custom converter must implement the IMultiValueConverter interface, identical to the WPF version.

In the example below, we use a MultiBinding to enable a button when two check boxes have been selected.

<CheckBox Name="chkLicenceAccepted" >I have accepted the Licence</CheckBox>
<CheckBox Name="chkConditionsAccepted" >I have accepted the Conditions</CheckBox>
<Button Content="Continue" >

    <Button.IsEnabled>
       <local:MultiBinding
          Source1="{Binding IsChecked, ElementName=chkLicenceAccepted}"
          Source2="{Binding IsChecked, ElementName=chkConditionsAccepted}"
          Converter="{local:MinNumberOfSetConverter}"
          ConverterParameter="2"
       />
    </    </Button.IsEnabled>
</Button>

As demonstrated above, the MultiBinding supports source bindings with ElementNames. RelativeSource is also supported allowing reference to target element by using Self or an ancestor in the visual tree by using AncestorType (new in Silverlight 5).

The implementation of berOfSetConverter is shown below. What we need above is just a logical AND converter, but to be useful in many situations I wrote it in a more general fashion supporting specifying the number of set (“true”) source values required to return true.

public classpublic class MinNumberOfSetConverter : MarkupExtension, IMultiValueConverter
{
   public object Convert(object[] values, Type targetType,
       object parameter, System.Globalization.CultureInfo culture)
   {
      int minNumberOfSet;
      // The parameter is minimum number of values to be set.
      // If not set all input values must be set to return true.
      if( !int.TryParse(parameter as String, out minNumberOfSet) ) 
                minNumberOfSet = values.Length;
      int numberOfSet = 0;
      for (int i = 0; i < values.Length; i++)
      {
         bool? boolValue = values[i] as bool?;
         if (boolValue.GetValueOrDefault()) numberOfSet++;
         if (numberOfSet >= minNumberOfSet) return true;
      }
      return numberOfSet >= minNumberOfSet;
   }

   public object[] ConvertBack(object value, Type[] targetTypes, 
        object parameter, System.Globalization.CultureInfo culture)
   {
      throw new NotSupportedException();
   }

   public override Object ProvideValue(IServiceProvider serviceProvider)
   {
      return this;
   }
}

To support convenient markup extension syntax, the converter derives from MarkupExtension, but this is not a requirement on the converter. To reuse the same converter in several locations, it can instead be defined as a resource and referred to using StaticResource

Conversion Back from Target to Source Values

This Silverlight MultiBinding also supports two-way binding with conversion from a single value to multiple values through the Converter’s ConvertBack method, similar to the WPF counterpart. In the example below, we present a length together with the unit in a text allowing the user to modify both in the same field. When the user modifies the text, the custom converter splits the quantity and the unit to its original fields:

<TextBlock Text="Length: " />
<TextBox Width="100" BindingValidationError="TextBox_BindingValidationError" >
  <TextBox.Text>
     <local:MultiBinding Mode="TwoWay"
                         NotifyOnValidationError="True"
                         ValidatesOnExceptions="true"
                         Converter="{local:LengthConverter}"
                         Source1="{Binding Length, Mode=TwoWay, 
                ValidatesOnExceptions=True}"
                         Source2="{Binding LengthUnit, Mode=TwoWay}"  />
  </TextBox.Text>
</TextBox>

To enable conversion back, Mode must be set to TwoWay on the MultiBinding and on the source bindings. The converter must also implement the IMultiValueConverter.ConvertBack method. In the example above, LengthConverter’s implementation looks like this:

public object[] ConvertBack(object value, Type[] targetTypes,
    object parameter, System.Globalization.CultureInfo culture)
{
    string lengthWithUnit = value as string;
    if (lengthWithUnit != null)
    {
        object[] result = new object[2];
        string[] parts = lengthWithUnit.Split(new Char[] { ' ' }, 
            StringSplitOptions.RemoveEmptyEntries);
        result[0] = Double.Parse(parts[0].ToString());
        if (parts.Length > 1)
        {
            result[1] = parts[1].Trim();
        }
        return result;
    }
    return null;
}

The ConvertBack is expected to return a Object[] array with values for each source. The input parameter targetType indicates the types (based on the current values). If ConvertBack returns null, no source will be updated. If an item in the result array is set to DependencyProperty.UnsetValue, the corresponding source will not be updated. In case the returned array is shorter than the number of sources, only the first sources will be updated.

In case an exception is thrown from the ConvertBack method, it will cause a validation error to be set on the target element, but only when the ValidatesOnExceptions property is set to true. Just like ordinary Bindings, you can also set the NotifyOnValidationError property to true to raise the bubbling BindingValidBindingValidationError event when the validation error state is changed. To get validation errors when individual sources are set, you can set the ValidatesOnExceptions or other validation related properties on the individual source bindings as well.

How It Works

To use the MultiBinding, you do not have to read this section, but if you are interested in the implementation details and the challenges I met when implementing this, please read on.

A Custom Binding Markup Extension

When a markup extension is applied to a property, its ProvideValue method is called. Since BindingBase and Binding have sealed ProvideValue methods, it is not possible to extend the binding mechanism by simple inheritance. Instead, we can create a new MarkupExtension derived class and let its ProvideValue return the result from an inner, programmatically created Binding instance. For most target properties, Binding’s ProvideValue returns a BindingExpression, but for style setter values, it returns the Binding itself.

For MultiBinding a new inner Binding instance is created each time the MultiBinding is applied, i.e., ProvideValue is called. The source of this binding is the SourceValues property of a MultiBindingExpression instance. Just like the BindingExpression, one MultiBindingExpression is created for each binding target instance:

public class MultiBinding : DependencyObject, IMarkupExtension<Object>
{
...
    public object ProvideValue(IServiceProvider serviceProvider)
    {
        // Some error checking code not shown in article text.
        IProvideValueTarget pvt = serviceProvider.GetService
        (typeof(IProvideValueTarget)) as IProvideValueTarget;

        DependencyObject target = pvt.TargetObject as DependencyObject;

        Binding resultBinding = ApplyToTarget(target);

        return resultBinding.ProvideValue(serviceProvider);
    }

    private Binding ApplyToTarget(DependencyObject target)
    {
        Seal();

        // Create new MultiBindingExpression to hold information about this multibinding
        MultiBindingExpression newExpression = new MultiBindingExpression(target, this);

        // Create new binding to expressions's SourceValues property
        PropertyPath path = new PropertyPath("SourceValues");
        Binding resultBinding = new Binding();
        resultBinding.Path = path;
        resultBinding.Source = newExpression;
        resultBinding.Converter = newExpression;
        resultBinding.Mode = Mode;
        resultBinding.UpdateSourceTrigger = UpdateSourceTrigger;
        resultBinding.TargetNullValue = TargetNullValue;
        resultBinding.ValidatesOnExceptions = ValidatesOnExceptions;
        resultBinding.NotifyOnValidationError = NotifyOnValidationError;
        resultBinding.ConverterCulture = ConverterCulture;
        resultBinding.FallbackValue = FallbackValue;
        return resultBinding;
    }
}

The MultiBindingExpression.SourceValues property holds the unconverted source values from the participating bindings. The final aggregation of the source values to the final target value involves invoking any user-provided IMultiValueConverter implementation and doing formatting according to the StringFormat setting. This is performed in the MultiBindingExpression.Convert method, which is an implementation of the standard single-value IValueConverter interface. As shown in the code-snippet above, the MultiBindingExpression newExpression is set as the Converter on the inner binding, not just the Source, to achieve this setup. In the first implementation, the aggregation was done before setting the SourceValues property, but to be able to use the binding culture and target type awareness, I found it better to let MultiBindingExpression implement the IValueConverter and use that as a converter for the source.

Hidden Attached Properties to Support Bindings Relative to Target

Another challenge was to support all type of source bindings relative to the target, including to the data context, named elements and RelativeSource. In the first implementation, these bindings were transferred to and managed by the MultiBindingExpression instance, one for each time the MultiBinding was applied. MultiBindingExpression then derived from FrameworkElement to support binding and DataContext. By binding the DataContext of the MultiBindingExpression to the DataContext of the target, it was possible to support binding relative to the target’s data context. This is similar to the approach described as "virtual trees" by Josh Smith. However, supporting ElementName and RelativeSource is much harder with this approach since the MultiBindingExpression is not a true part of the visual tree. Colin Ebenhardt has demonstrated a way to support ElementName, but I chose another solution based on attached properties, attached to the MultiBinding target object. These attached data properties are owned by the MultiBindingExpression and set when a MultiBindingExpression instance is created and applied to a target element. All source bindings from the MultiBinding are then reapplied to the attached data properties, placing them in the right context to use bindings relative to DataContext, named elements and RelativeSource. If the XAML code was saved after the MultiBinding has been applied, we would see the data properties as shown in the snippet below, where D0 and D1 are the name of the data properties, and MBE is an abbreviation for MultiBindingExpression:

<TextBlock Text="{z:MultiBinding Source1={Binding FirstName} 
    Source2={Binding LastName} StringFormat='%0 %1'}"
           MBE.MultiBindingExpression="..." MBE.D0="{Binding FirstName}" 
    MBE.D1="{Binding LastName}" /> 

To support any number of MultiBindings to the same target and any number of source bindings on a single MultiBinding, the attached data properties are created dynamically as needed. They are hidden from the designer since they do not have any getters or setters. To keep track of the mapping between the attached data properties and the multibinding an additional hidden MultiBindingExpressions attached property is set on target. This is set to a list of MultiBindingExpressions applied to the target element. Each MultiBindingExpression contains private member fields to keep track of what data property corresponds to which source property. Data properties are also used to store any binding to Converter, ConverterParameter and StringFormat, allowing them to be specified relative to the target element too. Whenever the value of a source binding changes, a common property change callback method registered for all data properties is invoked. This callback method will initiate a request to update the SourceProperty. This update will happen asynchronously by posting a request to the targets dispatcher, to allow several bindings to change values before aggregating the result, for instance when the data context changes.

A DependencyObject Derived Markup Extension to Support Binding

To support bindings to be specified for the MultiBinding markup extension properties, MultiBinding derives from DependencyObject. Interestingly, this is supported in Silverlight, but not in WPF, because in Silverlight markup extension can simply implement the IMarkupExtension<T> interface, whereas they must derive from the MarkupExtension, derived directly from Object, in WPF. Any binding from the MultiBinding instance is reapplied to an attached data property on the target when the MultiBinding is applied to the target. Bindings are detected using DependencyObject.ReadLocalValue which will return a BindingExpression when a binding exists for the source markup property. From this BindingExpression, we can get the ParentBinding and rebind it to an attached data property using BindingOperation.SetBinding:

// localValue is the value from DependencyObject.ReadLocalValue(<a MultiBinding property>)

DependencyProperty destProperty = GetDataProperty(dataPropertyIndex);

BindingExpression bindingExpression = localValue as BindingExpression;

if (bindingExpression != null)
{
    Binding propertyBinding = bindingExpression.ParentBinding;
    BindingOperations.SetBinding(Target, destProperty, propertyBinding);
}
else
{
    Target.SetValue(destProperty, localValue);
}

Support for MultiBindings in Styles

Yet another challenge was to support specifying MultiBindings as values in styles just like ordinary bindings can be used in styles to be reused in multiple places like this:

<Style x:Key="myStyle1" TargetType="TextBox">
    <Setter Property="Text"
            Value="{z:MultiBinding Mode=TwoWay, Source1={Binding Title, Mode=TwoWay}, 
            Converter={StaticResource stringFormatConverter}}" />
    <Setter Property="Tag"
            Value="{z:MultiBinding Source1={Binding Title}, 
            Converter={StaticResource stringFormatConverter}}" />
</Style>

Without special treatment for style setter, we would apply the attached data properties on the style Setter and create an inner binding to a single MultiBindingExpression that would be shared between all target elements using the style, resulting in unexpected behavior. What we want is to create a new MultiBindingExpression for each element the style is applied to. To achieve this, we have to detect when a MultiBinding is applied to a setter value property:

private static readonly PropertyInfo SetterValueProperty = 
        typeof(Setter).GetProperty("Value");

public object ProvideValue(IServiceProvider serviceProvider)
{

   IProvideValueTarget pvt = serviceProvider.GetService
        (typeof(IProvideValueTarget)) as IProvideValueTarget;

   if (pvt.TargetObject is Setter && pvt.TargetProperty == SetterValueProperty)
   {
      Setter setter = (Setter)pvt.TargetObject;
      ApplyToStyle(setter);
      return this;
   }
      …
}
private void ApplyToStyle(Setter setter)
{
    // Save the original Setter property for later use...
    m_styleProperty = setter.Property;

    // ... and replace it with an internal attached property.
    setter.Property = styleMultBindingProperties[currentStyleMultiBindingIndex];
    currentStyleMultiBindingIndex = (currentStyleMultiBindingIndex + 1) 
                    % MaxStyleMultiBindingsCount;
    Seal();
}

What we are doing in ApplyToStyle above is to actually replace the Setter's original Property property with an internal attached property. The original setter property is stored in m_styleProperty. When the style is applied and the attached property then is set, a registered property change callback for the attached property applies the multibinding to the property originally set in the style setter:

// Called when one of the styleMultiBindingProperties are changed
private static void OnStyleMultiBindingChanged
    (DependencyObject d, DependencyPropertyChangedEventArgs args)
{
   MultiBinding mb = (MultiBinding)args.NewValue;
   if (mb != null)
   {
     // Only apply multibinding from Style if no local value has been set.
     object existingValue = d.ReadLocalValue(mb.m_styleProperty);
     if (existingValue == DependencyProperty.UnsetValue)
     {
       // Ap       // Apply binding to target by creating
       // MultiBindingExpression and setting attached data properties.
        Binding resultBinding = mb.ApplyToTarget(d);
        // Set binding on the property originally defined by the style setter.
        BindingOperations.SetBinding(d, mb.m_styleProperty, resultBinding);
     }
  }
}

To support several MultiBindings for different properties in the same style, we are required to use different style multibinding attached properties for each of them. That is why we have an array of properties (created in the static class constructor, not shown above). To avoid having to create a new attached property for each setter, we reuse them in a round-robin fashion. As a consequence, there is a maximum number of setters with MultiBindings in the same Style, defined by the constant MaxStyleMultiBindingsCount, currently set to 10. It is very unlikely that more MultiBindings are applied in the same style, but this setting can be increased if necessary.

History

  • November 16, 2011
    • Initial version developed and tested with Silverlight 5 RC in Visual Studio 2010
  • May 8, 2014
    • Version 1.1 with upgrade to VS2013 and fixes mainly to avoid design-time errors

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Henrik Jonsson
Software Developer
Sweden Sweden
Henrik Jonsson is a Microsoft Professional Certified Windows Developer (MCPD) that currently works as an IT consultant in Västerås, Sweden.
 
Henrik has worked in several small and large software development projects in various roles such as architect, developer, CM and tester.
 
He regularly reads The Code Project articles to keep updated about .NET development and get new ideas. He has contributed with articles presenting some useful libraries for Undo/Redo, Dynamic Linq Sorting and a Silverlight 5 MultiBinding solution.

Comments and Discussions

 
QuestionRelativeSource example? PinmemberJoão Gala Louros19-Jun-12 16:46 
Hi,
 
can anyone provide an example using Relative Source? Here is what I am trying to do:
 
<TextBlock.Text><z:MultiBinding StringFormat="%0 (%1)">
                                    <z:BindingCollection>
                                        <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Label" />
                                        <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value" />
                                    </z:BindingCollection>
                                </z:MultiBinding></TextBlock.Text>
 
What do I need to fix?
 

Thanks
AnswerRe: RelativeSource example? PinmemberHenrik Jonsson20-Jun-12 10:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 18 Nov 2011
Article Copyright 2011 by Henrik Jonsson
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid