Colin Eberhardt's Technology Adventures

Developing a (very) Lookless Silverlight Radial Gauge Control

August 19th, 2010

This blog post describes the development of a lookless radial gauge control. In this post I will explore the use of an attached view model in order to move view specific properties and logic out of the control code in order to give a truly lookless control.

Today I had to get up far too early in order to catch an early morning flight to Copenhagen with a connection in Amsterdam. What to do for the six hours I would be travelling? Armed with a netbook and Visual Studio 2010 Express I thought it would be fun to have a go at developing a Silverlight gauge control. I know that there are already one or two free ones out there, with a decent looking one available on codeproject, however, it still felt like a good way to pass the time!

In order to make things a little more challenging I wanted to create a control that was truly lookless. So, what do I mean by this? Firstly a gauge control in its simplest sense displays the location of some indicator between a maximum and minimum value. There is nothing inherently circular about a gauge, thermometers are a good example of a linear gauge. So, I don’t want any ‘circular’ logic in the control itself. Secondly, custom controls often have certain expectations about the presence of named elements within their template. By this I mean that the template must contain, for example, a Path element called ‘needle’ which the control code will manipulate (The gauge published in the codeproject article above requires the presence of four named elements in the template). This forces certain constraints regarding how the control can be templated, this isn’t really lookless is it?

The following example shows the gauge control which I created, and the rest of this post describes the implementation:

<local:GaugeControl Value="65" Width="200" Height="200"  
                    Maximum="100" Minimum="50"
                    x:Name="gauge">
  <local:GaugeControl.QualitativeRange>
    <local:QualitativeRanges>
      <local:QualitativeRange Color="Yellow" Maximum="75"/>
      <local:QualitativeRange Color="Orange" Maximum="90"/>
      <local:QualitativeRange Color="Red" Maximum="100"/>
    </local:QualitativeRanges>
  </local:GaugeControl.QualitativeRange>
</local:GaugeControl>

The Starting Point

The first step was to create a Gauge custom control with Value, Maximum and Minimum dependency properties. The only logic defined within the control itself is to set the DataContext of the root visual element to the control instance itself. This is quite a common approach to control design, allowing elements within the template to bind to the control properties:

public override void OnApplyTemplate()
{
  base.OnApplyTemplate();
 
  Grid root = GetTemplateChild("LayoutRoot") as Grid;
  root.DataContext = this;
}

The first thing I added to the control template was the ‘face’ of the radial gauge. This is simply an Ellipse with a pretty gradient fill and stroke:

<!-- dial background and outer border -->
<Ellipse Stretch="Fill" StrokeThickness="8">
  <Ellipse.Fill>
    <RadialGradientBrush Center="0.5,0.5">
      <GradientStop Color="#EEF"/>
      <GradientStop Color="#99B" Offset="0.9"/>
      <GradientStop Color="#335" Offset="1"/>
    </RadialGradientBrush>
  </Ellipse.Fill>
  <Ellipse.Stroke>
    <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
      <GradientStop Color="#BBD" Offset="0"/>
      <GradientStop Color="#003" Offset="1"/>
    </LinearGradientBrush>
  </Ellipse.Stroke>
</Ellipse>

Adding the Needle

The next thing I added to the control was a needle. This is rendered using a simple Path with a LinearGradient in order to give it some sense of depth. I want the needle to have a length of approximately 70% of the gauge’s radius, a simple way to achieve this is to construct it within a Grid that uses ‘star’ widths / heights to provide a proportional layout and configure the Path to stretch to fill the cell it occupies:

<!-- the needle path -->              
<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="3*"/>
    <RowDefinition Height="7*"/>
    <RowDefinition Height="10*"/>
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
    <ColumnDefinition/>
    <ColumnDefinition/>
  </Grid.ColumnDefinitions>
 
  <Path Stretch="Uniform"
      Grid.Row="1" Grid.ColumnSpan="2"
      HorizontalAlignment="Center"
      Stroke="Black" StrokeThickness="0.5"
      Data="M 0,0 l 10,60 l -10, 40 l -10 -40">
    <Path.Fill>
      <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
        <GradientStop Color="DarkRed" Offset="0"/>
        <GradientStop Color="DarkRed" Offset="0.45"/>
        <GradientStop Color="Red" Offset="0.55"/>
        <GradientStop Color="Red" Offset="1"/>
      </LinearGradientBrush>
    </Path.Fill>
  </Path>
</Grid>

Here you can see how the needle is scaled by its parent Grid:

Rotating the needle to reflect the current Gauge Value is achieved quite simply via RotateTransform. However, this needs to be converted into a rotation angle which depends on the Gauge Maximum /Minimum values together with the overall angle of sweep on the gauge. I initially approach this problem by applying bindings via value converters and multibindings, however I found myself repeating the same conversion logic in numerous places within the template in order to render the ticks etc… Ideally the angle of rotation would be something that the template could bind to. The template DataContext is bound to the Gauge control itself, however as stated earlier I do not want ‘circular’ concepts to leak into the control.

An attached View Model

The solution I came up with for this problem was to create a view model that lives entirely within the control template that acts as an adapter for the Gauge, supplementing its properties with the needed ‘circular’ concepts. In keeping with my aims I could not instantiate this view model within the Gauge control itself, so instead it is created via an attached behaviour within the control template:

<Style TargetType="local:GaugeControl">
  <Setter Property="FontSize" Value="10"/>
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="local:GaugeControl">
        <Grid x:Name="LayoutRoot" >
          <Grid>
            <!-- attached the view model -->
            <local:RadialGaugeControlViewModel.Attach>
              <local:RadialGaugeControlViewModel/>
            </local:RadialGaugeControlViewModel.Attach>
 
            <!-- ... control template goes here ...  -->
          </Grid>
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

The view model defines the Attach property and in its change handler performs the logic required to bind to the DatatContext of the parent (which is the Gauge control itself), and set itself as the DataContext of the Grid to which it is being attached. This allows the rest of the template to bind to properties of the RadialGaugeControlViewModel.

public class RadialGaugeControlViewModel : FrameworkElement, INotifyPropertyChanged
{
  #region Attach attached property
 
  public static readonly DependencyProperty AttachProperty =
      DependencyProperty.RegisterAttached("Attach", typeof(object), typeof(RadialGaugeControlViewModel),
          new PropertyMetadata(null, new PropertyChangedCallback(OnAttachChanged)));
 
  public static RadialGaugeControlViewModel GetAttach(DependencyObject d)
  {
    return (RadialGaugeControlViewModel)d.GetValue(AttachProperty);
  }
 
  public static void SetAttach(DependencyObject d, RadialGaugeControlViewModel value)
  {
    d.SetValue(AttachProperty, value);
  }
 
  /// <summary>
  /// Change handler for the Attach property
  /// </summary>
  private static void OnAttachChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    Grid targetElement = d as Grid;
    RadialGaugeControlViewModel viewModel = e.NewValue as RadialGaugeControlViewModel;
 
    // handle the loaded event
    targetElement.Loaded += new RoutedEventHandler(Grid_Loaded);
 
  }
 
  /// <summary>
  /// Handle the Loaded event of the Grid to enable the attached
  /// view model to bind to properties of the Grid Parent element
  /// </summary>
  static void Grid_Loaded(object sender, RoutedEventArgs e)
  {
    FrameworkElement targetElement = sender as FrameworkElement;
    FrameworkElement parent = targetElement.Parent as FrameworkElement;
 
    // use the attached view model as the DataContext of the element it is attached to
    RadialGaugeControlViewModel attachedModel = GetAttach(targetElement);
    targetElement.DataContext = attachedModel;
 
    // bind the DataContext of the view model to the DataContext of the parent.
    attachedModel.SetBinding(RadialGaugeControlViewModel.DataContextProperty,
      new Binding("DataContext")
      {
        Source = parent
      });
  }
}

It is now possible to expose a property on the view model which provides the Gauge Value as an angle:

public double ValueAngle
{
  get
  {
    if (Gauge == null)
      return 0.0;
 
    return ValueToAngle(Gauge.Value);
  }
}
 
private double ValueToAngle(double value)
{
  double minAngle = -150;
  double maxAngle = 150;
  double angularRange = maxAngle - minAngle;
 
  return (value - Gauge.Minimum) / (Gauge.Maximum - Gauge.Minimum) *
      angularRange + minAngle;
}

This can then be bound to in the template in order to rotate the needle. For an extra ‘flourish’ a drop shadow is also added to the needle which binds to this same rotation angle in order to give a subtle ‘3D’ effect:

<Path Stretch="Uniform"
    Grid.Row="1" Grid.ColumnSpan="2"
    HorizontalAlignment="Center"
    Stroke="Black" StrokeThickness="0.5"
    Data="M 0,0 l 10,60 l -10, 40 l -10 -40"
    RenderTransformOrigin="0.5,1">
  <Path.RenderTransform>
    <!-- rotate the needle -->
    <RotateTransform Angle="{Binding Path=ValueAngle}"/>
  </Path.RenderTransform>
  <Path.Fill>
    <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
      <GradientStop Color="DarkRed" Offset="0"/>
      <GradientStop Color="DarkRed" Offset="0.45"/>
      <GradientStop Color="Red" Offset="0.55"/>
      <GradientStop Color="Red" Offset="1"/>
    </LinearGradientBrush>
  </Path.Fill>
  <Path.Effect>
    <DropShadowEffect Color="Black" Direction="{Binding Path=ValueAngle}"
                      BlurRadius="3"
                      Opacity="0.6"
                      ShadowDepth="5"/>
  </Path.Effect>
</Path>

Adding a Scale

The gauge control needs to have tick marks and labels render around the dial face at regularly spaced intervals between the Maximum and Minimum values. Here the view model comes into its own by providing a list of ‘Tick’ value objects, each of which provide the view with the required information to render tick marks and their labels:

public IEnumerable<Tick> MajorTicks
{
  get
  {
    if (Gauge == null)
      yield break;
 
    double tickSpacing = (Gauge.Maximum - Gauge.Minimum) / 10;
    for (double tick = Gauge.Minimum; tick <= Gauge.Maximum; tick += tickSpacing)
    {
      yield return new Tick()
      {
        Angle = ValueToAngle(tick),
        Value = tick.ToString("N0"),
        Parent = this
      };
    }
  }
}
 
public class Tick
{
  public double Angle { get; set; }
  public string Value { get; set; }
  public RadialGaugeControlViewModel Parent { get; set; }
}

The XAML which renders the major tick marks uses an ItemsControl to create each tick instance:

<!-- major ticks -->
<ItemsControl ItemsSource="{Binding Path=MajorTicks}"
              VerticalAlignment="Center" HorizontalAlignment="Center">
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <Canvas></Canvas>
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Ellipse Fill="Black" Width="8" Height="8">
        <Ellipse.RenderTransform>
          <TransformGroup>
            <!-- centre the ellipse -->
            <TranslateTransform X="-4" Y="-4"/>
            <!-- offset to the edge of the gauge -->
            <TranslateTransform X="0"
                Y="{Binding Path=Parent.GridHeight, Converter={StaticResource ScaleFactorConverter},
                                                                 ConverterParameter=-0.37}"/>
            <!-- rotate -->
            <RotateTransform Angle="{Binding Angle}"/>
          </TransformGroup>
        </Ellipse.RenderTransform>
      </Ellipse>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

As you can see each tick is simply an ellipse. The clever part is how each Ellipse is transformed to position it appropriately. It is first centre to make subsequent transforms a little simpler, it is then translated y an offset which moves it to the edge of the gauge face. The offset factor is computed as some fraction of the overall size of the gauge control. In order to achieve this I reluctantly had to angle SizeChanged events on the template Grid in order to expose its ActualHeight / ActualWidth, this is because ElementName binding on these properties appears to be broken.

On attachment the view model handles SizeChanged events as follows:

/// <summary>
/// Handle SizeChanged events from the grid so that we can inform elements
/// of changes in the ActualHeight / ActualWidth
/// </summary>
private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
{
  OnPropertyChanged("GridHeight");
  OnPropertyChanged("GridWidth");
 
  Grid.Clip = new EllipseGeometry()
  {
    RadiusX = _grid.ActualWidth / 2,
    RadiusY = _grid.ActualHeight / 2,
    Center = new Point(_grid.ActualWidth / 2, _grid.ActualHeight / 2)
  };
}
 
public double GridWidth
{
  get { return _grid.ActualWidth; }
}
 
public double GridHeight
{
  get { return _grid.ActualHeight; }
}

Adding tick labels and minor tick marks both use a simple variation on the above described approach:

I also added a ‘qualitative value’ range which renders a colour coded band beneath the needle. Again, this uses variations on the same approach, with the view model adapting the properties of the Gauge control and the template binding to these properties, together with the Grid size information in order to provide any required scaling.

The last flourish was to add a ‘glass’ effect to the Gauge. This was ‘borrowed’ directly from this fantastic codeproject article on creating ‘round glassy buttons’.

The finished Gauge is shown below, where its value is bound to a Slider control:

Conclusions

I am pretty happy with how this Gauge control turned out, visually I think it looks pretty good. I am also happy that I have succeeded in my initial aim of making it completely lookless. The attached view model within the control template is an interesting approach that moves view specific concepts (in this case angular properties) into the view, which is where the belong.

This radial Gauge control could certainly be improved to allow a more flexible scale calculation. Also, the RadialGaugeControlViewModel could also expose some of the view specific properties such as the radial sweep angle (currently hard-coded to 300 degrees) allowing them to be set in the template. For now, I think I will leave this control as it is.

Tomorrow I am on another early flight, this time heading back home. I might take this as an opportunity to provide a view for this control making use of a different attached view model.

You can download the full source for this article: GaugeControl.zip

(Apologies for the lack of project structure, this code was written using VS 2010 Express).

Regards, Colin E.

Silverlight MultiBinding updated, adding support for ElementName and TwoWay binding

August 12th, 2010

This blog post describes an update to the Silverlight 4 MultiBinding technique I blogged about a couple of months ago to add support for ElementName binding and TwoWay binding.

A few months ago I posted an update to my MultiBinding solution for Silverlight 4. This technique allows you to perform the same kind of multibindings which are possible in WPF, where a property value is bound to multiple sources via a special value converter that implements the IMultiValueConverter interface, which describes how these values are combined. This update proved popular once again, and I received a few requests to add support for ElementName and TwoWay binding. I like challenge! This blog post describes how these two features were implemented, but if you just want to grab the code, you will find the link at the end of this article.

A brief recap

Before I go into the implementation details for the new features I will provide a brief recap of how my multibinding solution works. The XAML for creating a multibinding looks like the following:

<TextBlock Foreground="White" 
  <local:BindingUtil.MultiBindings>
    <local:MultiBindings>
      <local:MultiBinding TargetProperty="Text"
                          Converter="{StaticResource TitleConverter}">
        <local:BindingCollection>
          <Binding Path="Surname"/>
          <Binding Path="Forename"/>
        </local:BindingCollection>
      </local:MultiBinding>
      <local:MultiBinding TargetProperty="ToolTipService.ToolTip"
                          Converter="{StaticResource TitleConverter}">
        <local:BindingCollection>
          <Binding Path="Surname"/>
          <Binding Path="Forename"/>
        </local:BindingCollection>
      </local:MultiBinding>
    </local:MultiBindings>
  </local:BindingUtil.MultiBindings>
</TextBlock>

Here the Text property and attached ToolTip property of the TextBlock are bound to both the Surname and Forename property of the business object which is set as the DataContext of our view.

The value converter is as follows:

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

In the example below you can see that as you change the forename or surname, the title text and tooltip are updated by the multibinding.

So how does this work?

When a multibinding is created and added to an element via the BindingUtil.MultiBindings attached property, the MultiBinding instance is added to a virtual-branch, this is a branch of the visual tree that obtains the DataContext of the element to which it is bound, but is not added to the visual tree itself. The MultiBinding then creates a BindingSlave instance for each of the given Bindings. These ‘slave’ elements inherit the MultiBinding DataContext and are used to evaluate each of the individual Bindings. The MultiBinding instance aggregate the results of each binding with the IMultiValueConverter used to compute the ConvertedValue property which is bound to the target property on the element to which this MultiBinding as attached.

ElementName binding

So why does the above solution not work if one of the multibindings uses ElementName to locate the source rather than using the inherited DataContext? The problem here is that the MultiBinding and its BindingSlave instances are located in a virtual branch and are therefore not in the same namescope as the target element. As a result, they cannot perform look up of named elements.

In order to solve this problem I created a BindingSlave subclass specifically for ElementName binding. This slave locates the source element named via the ElementName property and sets it as the Source of the binding it uses to compute its Value property.

The only minor complication I encountered is that the MultiBinding might be constructed before the element referenced via ElementName. For this reason the binding slave must handle LayoutUpdated events to ensure that the named element is located if it is constructed after the multibinding.

public class ElementNameBindingSlave : BindingSlave
{
  private FrameworkElement _multiBindingTarget;
 
  /// <summary>
  /// The source element named in the ElementName binding
  /// </summary>
  private FrameworkElement _elementNameSource;
 
  private Binding _binding;
 
  public ElementNameBindingSlave(FrameworkElement target, Binding binding)
  {
    _multiBindingTarget = target;
    _binding = binding;
 
    // try to locate the named element
    ResolveElementNameBinding();
 
    _multiBindingTarget.LayoutUpdated += MultiBindingTarget_LayoutUpdated;
  }
 
  /// <summary>
  /// Try to locate the named element. If the element can be located, create the required
  /// binding.
  /// </summary>
  private void ResolveElementNameBinding()
  {
    _elementNameSource = _multiBindingTarget.FindName(_binding.ElementName) as FrameworkElement;
    if (_elementNameSource != null)
    {
      SetBinding(BindingSlave.ValueProperty, new Binding()
      {
        Source = _elementNameSource,
        Path = _binding.Path,
        Converter = _binding.Converter,
        ConverterParameter = _binding.ConverterParameter
      });
    }
  }
 
  private void MultiBindingTarget_LayoutUpdated(object sender, EventArgs e)
  {
    // try to locate the named element 
    ResolveElementNameBinding();
  }
}

The example below demonstrates element name binding by binding the Value of two Slider elements to a TextBlock which displays the sum of the two values.

<TextBlock Grid.Column="1">
  <local:BindingUtil.MultiBindings>
    <local:MultiBindings>
      <local:MultiBinding TargetProperty="Text"
                          Converter="{StaticResource SliderValueConverter}">
        <local:BindingCollection>
          <Binding ElementName="sliderOne" Path="Value"/>
          <Binding ElementName="sliderTwo" Path="Value"/>
        </local:BindingCollection>
      </local:MultiBinding>
    </local:MultiBindings>
  </local:BindingUtil.MultiBindings>
</TextBlock>
 
<Slider x:Name="sliderOne" Value="10"/>
 
<Slider x:Name="sliderTwo"  Value="20"/>

Here is the value converter that is used to compute the sum:

public class SliderValueConverter : IMultiValueConverter
{
 
  public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    if (values.Length != 2 ||
        values[0] == null ||
        values[1] == null)
      return null;
 
    return (double)values[0] + (double)values[1];
  }
 
  ...
}

You could of course use element name binding to combine the surname / forename in the first example in this blog post, but in this context it is more likely that you will want to back your UI with a business object. It is also possible to mix element name and regular (i.e. Source=DataContext) binding.

I find multibindings which use ElementName references most useful when creating complex UI layouts that do not lend themselves to being implemented using Panels. For a good example see my article on the implementation of a BulletGraph control which makes extensive use of this technique in order that the layout of the control is performed entirely within XAML.

TwoWay Binding

A two-way multibinding must be able to handle updates to the combined value and split it up into its constituent parts in order to update the multiple source bindings. In the first example where a forename and surname property are combined into a “surname, forename” string it is possible to convert back the other way, but in the example above where the values of two Sliders are combined a reverse conversion is not possible.

Modifying this multibinding solution to permit two way binding was relatively straightforward. A property changed event handler was added to the MultiBindig.ConverterValue (which is bound to the multi binding target) so that we can determine when changes have been made. This handler then uses the IMultiValueConverter to convert the value into the multiple source properties and updates the binding slaves accordingly:

/// <summary>
/// Handles property changes for the ConvertedValue property
/// </summary>
private void OnConvertedValuePropertyChanged()
{
    OnPropertyChanged("ConvertedValue");
 
    // if the value is being updated, but not due to one of the multibindings
    // then the target property has changed.
    if (!_updatingConvertedValue)
    {
      // convert back
      object[] convertedValues = Converter.ConvertBack(ConvertedValue, null,
          ConverterParameter, CultureInfo.InvariantCulture);
 
      // update all the binding slaves
      if (Children.Count == convertedValues.Length)
      {
        for (int index = 0; index < convertedValues.Length; index++)
        {
          ((BindingSlave)Children[index]).Value = convertedValues[index];
        }
      }
    }
}

In order to use two way multibinding both the multibinding mode and the bindings within the BindingCollection must be set to TwoWay. See the example given below:

<TextBlock Text="Surname:"/>
<TextBox  Grid.Column="1" Text="{Binding Path=Surname, Mode=TwoWay}"/>
 
<TextBlock Grid.Row="1" Text="Forename:"/>
<TextBox Grid.Row="1"  Grid.Column="1" Text="{Binding Path=Forename, Mode=TwoWay}"/>
 
<TextBlock Grid.Row="2" Text="Combined:"/>
<TextBox Grid.Row="2"  Grid.Column="1" >
  <local:BindingUtil.MultiBindings>
    <local:MultiBindings>
      <local:MultiBinding TargetProperty="Text"
                        Converter="{StaticResource TitleConverter}"
                        Mode="TwoWay">
        <local:BindingCollection>
          <Binding Path="Surname" Mode="TwoWay"/>
          <Binding Path="Forename" Mode="TwoWay"/>
        </local:BindingCollection>
      </local:MultiBinding>
    </local:MultiBindings>
  </local:BindingUtil.MultiBindings>
</TextBox>

You can have try of two way multibinding below where the values of the forename and surname text boxes are combined in the box below. However, you ca also make updates to the combined result which will then be converted back into its constituent parts:

And here is the value converter:

public class TitleConverter : IMultiValueConverter
{
  #region IMultiValueConverter Members
 
  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);
  }
 
  public object[] ConvertBack(object value, Type[] targetTypes,
    object parameter, System.Globalization.CultureInfo culture)
  {
    string source = value as string;
    var pos = source.IndexOf(", ");
 
    string forename = source.Substring(pos + 2);
    string surname = source.Substring(0, pos);
 
    return new object[] { forename, surname };
  }
 
  #endregion
}

So, there you have it, two-way and element name multibinding :-)

You can download the full source, including a WPF build (thanks to Stefan Olson) here: SLMultiBindingUpdated.zip

Now … go forth and multibind.

Regards, Colin E.

Silverlight MultiBinding solution for Silverlight 4

May 10th, 2010

In this post I describe an update to the Silverlight MultiBinding solution I presented last year. This update includes support for Silverlight 4, attached properties and multiple bindings on a single object.

UPDATE: I have updated this code to include ElementName and TwoWay binding. Grab the latest copy here.

MultiBinding is a WPF feature that allows you to bind a single property to a number of sources, with the source values being combined by a value converter. This is a feature that is missing from Silverlight. About a year ago I developed a MultiBinding solution for Silverlight, which has proven very popular! I even had an email from an Microsoft Attorney asking if they could use it in the Silverlight Facebook client (How cool is that :-) ). I was also very happy when Stefan Olson made a few updates to this code to allow multiple MultiBindings on a single object and spotted the SL4 issue. This blog post is a quick demonstration of these new features …

The following application is an example of MultiBindings in action:

In the above application, the TextBox at the top of the page is bound to both the surname and forename properties of our data-object via converter that takes the first letter of the forename and the surname. If you edit the surname or forename (hitting enter or changing focus to commit), the title is updated automatically. The title tooltip is bound to the all three object properties via a different value converter that concatenates all three.

The XAML for this binding is show below:

<TextBlock x:Name="Block" Foreground="White" FontSize="13" Margin="5,0,0,0">
    <local:BindingUtil.MultiBindings>
        <local:MultiBindings>
            <local:MultiBinding TargetProperty="Text"
                                Converter="{StaticResource TitleSummaryConverter}">
                <local:MultiBinding.Bindings>
                    <local:BindingCollection>
                        <Binding Path="Surname"/>                            
                        <Binding Path="Forename"/>
                        </local:BindingCollection>
                </local:MultiBinding.Bindings>
            </local:MultiBinding>
            <local:MultiBinding TargetProperty="ToolTipService.ToolTip"
                                Converter="{StaticResource TitleConverter}">
                <local:MultiBinding.Bindings>
                    <local:BindingCollection>
                        <Binding Path="Surname"/>                            
                        <Binding Path="Forename"/>
                        <Binding Path="Age"/>
                    </local:BindingCollection>
                </local:MultiBinding.Bindings>
            </local:MultiBinding>
        </local:MultiBindings>
    </local:BindingUtil.MultiBindings>
</TextBlock>

In the above you can see that our TextBlock has two multibindings, one on Forename and Surname, and the other which includes all three properties. Note, the second multibinding is on the ToolTipService.ToolTip attached property.

You can download the source for Silverlight MultiBinding here: SLMultiBindingUpdate.zip – thanks again to Stefan Olson who added WPF support to this technique.

If you are interested in the technical details of how this works, I refer you to the original article which describes how the code builds a ‘virtual’ branch on the visual tree in order to evaluate your bindings:

I hope this update is of use to SL4 developers. If you have any feedback, please leave a comment below.

You can download the source for Silverlight MultiBinding here: SLMultiBindingUpdate.zip

Regards, Colin E.

Silverlight MultiBindings, How to attached multiple bindings to a single property.

June 25th, 2009

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.

UPDATE: Silverlight 4, attached properties and multiple property bindings are all supported in my latest update here.

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.

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.