Click here to Skip to main content
Email Password   helpLost your password?

This blog posts describes a technique for associating multiple bindings with a single dependency property within Silverlight applications. WPF already has this functionality in the form of MultiBindings, the code in this post emulates this function.

The simple application below demonstrates this technique, where there are three data-entry text boxes bound to the individual properties of a simple Person object, with the title text block being bound to both the Forename and Surname properties. Try editing the surname or forename fields and watch as the title is updated.

app.png 

[CodeProject does not support Silverlight applets, see the above applicaton in action on my blog

The XAML for this application looks something like this (superfluous properties/ elements removed for clarity):

<TextBlock Foreground="White" FontSize="13">
    <local:BindingUtil.MultiBinding>
        <local:MultiBinding TargetProperty="Text" Converter="{StaticResource TitleConverter}">
            <Binding Path="Surname"/>                            
            <Binding Path="Forename"/>
        </local:MultiBinding>
    </local:BindingUtil.MultiBinding>
</TextBlock>
 
<TextBlock Text="Surname:"/>
<TextBox  Text="{Binding Path=Surname, Mode=TwoWay}"/>
 
<TextBlock Text="Forename:"/>
<TextBox Text="{Binding Path=Forename, Mode=TwoWay}"/>
 
<TextBlock Text="Age:"/>
<TextBox Text="{Binding Path=Age, Mode=TwoWay}"/>

The Solution

My solution to the problem of multi-binding was to introduce a class, MultiBinding which is associated with the element which has out multi-binding target property via the BindingUtil.MultiBinding attached property. The following diagram details my idea:

multibinding

The Forename and Surname bindings are bound to properties of the MultiBinding (Exactly which properties we will get onto in a minute). The MultiBinding has an associated Converter of type IMultiValueConverter, this client supplied class implements the conversion process, as shown below:

public class TitleConverter : IMultiValueConverter
{
 
  public object Convert(object[] values, Type targetType,
    object parameter, System.Globalization.CultureInfo culture)
  {
    string forename = values[0] as string;
    string surname = values[1] as string;
 
    return string.Format("{0}, {1}", surname, forename);
  }
}

The IMultiValueConverter interface is much the same as the IValueConverter, except in this case an array of objects are passed to the converter, with each object containing the current bound value for each of our bindings in order.

With this value converter, the MultiBinding class can detect changes in the two bindings, then, recompute the ConvertedValue which is bound to the target property of our TextBlock. This was my initial idea, and it certainly sound quite simple, however it was not quite as easy as it seams on first inspection!

Hijacking the DataContext

Typically when defining a binding we omit the Source property, e.g. {Binding Path=Forename}. When the Binding which this expression represents is associated with an element, the binding source will be the (possibly inherited) DataContext of the target element. Therefore, in order to allow binding on the MultiBinding class, it must be a FrameworkElement, this gives us the DataContext property and the SetBinding() method.

However, there is a problem; each element’s DataContext is inherited from its parent within the visual tree. Our MultiBinding is not within the visual tree and we do not want it to be, therefore it will not participate in DataContext inheritance. What we need to do is ensure that when DataContext changes on the element which the MultiBinding is associated with it, that we ‘push’ this DataContext onto the MultiBinding. With WPF this is easy, FrameWorkElement exposes a DataContextChanged event, (for DPs that do not expose events there’s always the DependencyPropertyDescriptor). However, with Silverlight, neither of these options are available.

My solution here is to create a new attached property and attach it to the target element (our TextBlock in this case), which piggy-backs the DataContext. The code below is from the BindingUtil class, when the MultiBinding class is associated with the target element as an attached property, we also bind its attached DataContextPiggyBack property. We define a static method which is invoked whenever the DatatContext of the target element changes, and here we ‘push’ this new DataContext to the MultiBinding class.

/// <summary>
/// Invoked when the MultiBinding property is set on a framework element
/// </summary>
private static void OnMultiBindingChanged(DependencyObject depObj,
  DependencyPropertyChangedEventArgs e)
{
  FrameworkElement targetElement = depObj as FrameworkElement;
 
  // bind the target elements DataContext, to our DataContextPiggyBack property
  // this allows us to get property changed events when the targetElement
  // DataContext changes
  targetElement.SetBinding(BindingUtil.DataContextPiggyBackProperty, new Binding());
}
 
 
public static readonly DependencyProperty DataContextPiggyBackProperty =
    DependencyProperty.RegisterAttached("DataContextPiggyBack",
        typeof(object), typeof(BindingUtil), new PropertyMetadata(null,
              new PropertyChangedCallback(OnDataContextPiggyBackChanged)));
 
public static object GetDataContextPiggyBack(DependencyObject d)
{
  return (object)d.GetValue(DataContextPiggyBackProperty);
}
 
public static void SetDataContextPiggyBack(DependencyObject d, object value)
{
  d.SetValue(DataContextPiggyBackProperty, value);
}
 
/// <summary>
/// Handles changes to the DataContextPiggyBack property.
/// </summary>
private static void OnDataContextPiggyBackChanged(DependencyObject d,
                                                              DependencyPropertyChangedEventArgs e)
{
  FrameworkElement targetElement = d as FrameworkElement;
 
  // whenever the targeElement DataContext is changed, copy the updated property
  // value to our MultiBinding.
  MultiBinding relay = GetMultiBinding(targetElement);
  relay.DataContext = targetElement.DataContext;
}

Creating targets for the bindings

The MultiBinding class needs to have a property which is a collection of type Binding:

[ContentProperty("Bindings")]
public class MultiBinding : Panel, INotifyPropertyChanged
{
 
  ...
 
  /// <summary>
  /// The bindings, the result of which are supplied to the converter.
  /// </summary>
  public ObservableCollection<Binding> Bindings { get; set; }
 
  ...
}

(Note the use of the ContentProperty attribute, which means that we do not have to explicitly detail Binding collection in XAML using the property element syntax). The problem is, in order for these bindings to be evaluated, they need to be bound to a target property. We could add a number of ‘dummy’ properties to MultiBinding, PropertyOne, PropertyTwo, etc … and bind to these, however this approach is cumbersome and limited.

The solution here is to make MultiBinding a Panel, this allows it to have child elements, each of which will inherit its DataContext. When the MultiBinding class is initialised at the point it is attached, the Initialise method is invoked. This method creates an instance of BindingSlave, a simple FrameworkElement subclass with a single Value property which raises PropertyChanged events when this property changes, for each binding:

/// <summary>
/// Creates a BindingSlave for each Binding and binds the Value
/// accordingly.
/// </summary>
internal void Initialise()
{
  foreach (Binding binding in Bindings)
  {
    BindingSlave slave = new BindingSlave();
    slave.SetBinding(BindingSlave.ValueProperty, binding);
    slave.PropertyChanged += new PropertyChangedEventHandler(Slave_PropertyChanged);
    Children.Add(slave);
  }            
}

Whenever a slave property changes, the MultiBinding event handler obtains the current bound values and uses them to re-evaluate the converter:

/// <summary>
/// Invoked when any of the BindingSlave's Value property changes.
/// </summary>
private void Slave_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
  List<object> values = new List<object>();
  foreach (BindingSlave slave in Children)
  {
    values.Add(slave.Value);
  }
  ConvertedValue = Converter.Convert(values.ToArray(), typeof(object), ConverterParameter,
    CultureInfo.CurrentCulture);
}

The ConvertedValue property is bound to the target property of the target element, and will be updated to reflect this change.

This method is very similar to one which Josh Smith described in his codeproject article on the concept of Virtual Branches. The MultiBinding and BindingSlave instances can be thought of as a virtual branch to our visual tree:

virtualbranch

Download Sources

You can download the full sourcecode for this project here: slmultibinding.zip

A final word on MVVM

The MVVM pattern is very popular in Silverlight and WPF application development. With this pattern, your view’s DataContext is bound to your view-model. With this pattern in place, the need for multi-bindings can be removed (Josh Smith goes further to moot the concept of removing value converters altogether). In our example the PersonViewModel class would simply expose a Title property which performs the same function as the TitleConverter. So does this render my technique completely redundant?

I don’t think so. Whilst MVVM is a great pattern, there are times where adding another layer to your application may be undesirable, especially if your primary aim is simplicity. Furthermore, I do not like being forced into using a specific pattern simply because the framework itself is lacking in functionality. The MVVM pattern is great for building skinnable applications, and allowing UI unit testing, however, if I do not need either of these features, I would prefer not to MVVM.

Regards, Colin E.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralAnother approach
Marcel Pletosu
9:08 21 Dec '09  
Interesting approach, however, for the sake of simplicity I would just use the Binding without a PropertyPath and code a converter to do the concatenation.

Something similar to the code below should do it.
XAML
"{Binding Converter={StaticResource PersonTitleConverter}}" />

GeneralRe: Another approach
Colin Eberhardt
1:50 24 Dec '09  
Hi Marcel,

I have used that approach myself in the past. It is just fine for one-time binding, however if you need to keep your multi-binding target synchronized, it does not work. A Binding with an empty property path is not re-evaluated when the source fires PropertyChanged events.

I think this stems from the fact that there is no convention for what a class should do if all of its properties change. Some people fire PropertyChanged events with an empty or null property name, however this is not recognised by the framework.

You can, however, create a fabricated Path, which you target with your PropertyChanged events:

http://www.scottlogic.co.uk/blog/colin/2009/04/binding-a-silverlight-datagrid-to-dynamic-data-part-2-editable-data-and-inotifypropertychanged/[^]

However, this results in some slightly odd business objects!

Regards, Colin E.
GeneralGreat article
eslsys
3:13 30 Jun '09  
Very informative. Many thanks
GeneralRe: Great article
Colin Eberhardt
3:21 30 Jun '09  
Thanks for the feedback Smile

Regards, Colin E.
Generalgreat article, and smart MVVM statement
christoph braendle
0:52 26 Jun '09  
There are cases MVVM is the best solution, but there is as many where not.
A lot of people tend to follow hypes; sometimes beeing aware of what is
happening gets lost. Fresh air with this article, good on you. Smile

The way you explain things is great!

Cheers Christoph
GeneralRe: great article, and smart MVVM statement
Colin Eberhardt
1:10 26 Jun '09  
Hi Christoph,

Thanks for your kind comments. Glad you liked the article.

I have had similar feelings about MVVM for a while - it seems that you cannot write a WPF or Silverlight article without dropping MVVM into the mix. It is a great pattern if used appropriately, however it is not the only way of doing things.

Personally I would like to see a bit less MVVM and a bit more point-and-click in WPF development to help bring these technologies to the masses in the same way WinForms has.

Colin E.
GeneralRe: great article, and smart MVVM statement
Pete O'Hanlon
12:07 28 Jun '09  
Colin Eberhardt wrote:
Personally I would like to see a bit less MVVM


Shhhh. They don't like to hear you talking like that. Poke tongue

The thing is, I'm happy for WPF articles to push MVVM - it might help raise the skill level of developers if they are exposed to patterns. Mind you - in lots of my blog samples, I don't use MVVM because it can end up obfuscating the techniques I'm trying to espouse.

"WPF has many lovers. It's a veritable porn star!" - Josh Smith

As Braveheart once said, "You can take our freedom but you'll never take our Hobnobs!" - Martin Hughes.


My blog | My articles | MoXAML PowerToys | Onyx



GeneralThis is one helluva article
Pete O'Hanlon
12:39 25 Jun '09  
Colin - I'm running out of superlatives for this. Great job, and a welcome addition to the toolkit - and one that may tempt me away from the pure MVVM path every now and again.

"WPF has many lovers. It's a veritable porn star!" - Josh Smith

As Braveheart once said, "You can take our freedom but you'll never take our Hobnobs!" - Martin Hughes.


My blog | My articles | MoXAML PowerToys | Onyx



GeneralRe: This is one helluva article
Colin Eberhardt
22:08 25 Jun '09  
Thanks Pete,

Really appreciate your positive feedback. It's comments like this that keep me up late at night writing code Smile

Don't worry, I will not tell the Disciples that I have dirtied your mind and lead you away from the righteous path of MVVM Wink

Colin E.
GeneralRe: This is one helluva article
Pete O'Hanlon
4:02 26 Jun '09  
Colin Eberhardt wrote:
I will not tell the Disciples that I have dirtied your mind and lead you away from the righteous path of MVVM


Do you want some pictures. Nudge nudge, wink wink, say no more.

"WPF has many lovers. It's a veritable porn star!" - Josh Smith

As Braveheart once said, "You can take our freedom but you'll never take our Hobnobs!" - Martin Hughes.


My blog | My articles | MoXAML PowerToys | Onyx




Last Updated 11 Nov 2009 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010