Binding multiple sources to a single target property

In WPF, there is another, more common way to data bind to multiple binding sources at once and to perform some sort of conversion from the various values to a single output value. In order to achieve this, we need to use a MultiBinding object in conjunction with a class that implements the IMultiValueConverter interface.

The MultiBinding class enables us to declare multiple binding sources and a single binding target. If the Mode or UpdateSourceTrigger properties of the MultiBinding class are set, then their values are inherited by the contained binding elements, unless they have different values set explicitly.

The values from the multiple binding sources can be combined in one of two ways; their string representations can be output using the StringFormat property, or we can use a class that implements the IMultiValueConverter interface to generate the output value. This interface is very similar to the IValueConverter interface, but works with multiple data bound values instead.

When implementing the IMultiValueConverter interface, we do not set the ValueConversion attribute that we are accustomed to setting in the IValueConverter implementations that we have created.

In the Convert method that we need to implement, the value input parameter of type object from the IValueConverter interface is replaced by an object array named values, which contains our input values.

In the ConvertBack method, we have an array of type Type for the types of the binding targets and one of type object for the return types. Apart from these slight differences, these two interfaces are the same. Let's look at an example to help clarify the situation.

Imagine a scenario where a healthcare application needs to display a patient's weight measurements over time. It would be helpful if we could output an indicator of whether each consecutive measurement was higher or lower than the previous one, to highlight any unhealthy trends.

 

This can be implemented using the RelativeSource.PreviousData property mentioned earlier, a MultiBinding object and an IMultiValueConverter class. Let's first take a look at how we implement the IMultiValueConverter interface:

using System; 
using System.Globalization;
using System.Windows;
using System.Windows.Data; namespace CompanyName.ApplicationName.Converters { public class HigherLowerConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
if (values == null || values.Length != 2 ||
!(values[0] is int currentValue) ||
!(values[1] is int previousValue))
return DependencyProperty.UnsetValue; return currentValue > previousValue ? "->" : "<-"; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { return new object[2] { DependencyProperty.UnsetValue, DependencyProperty.UnsetValue }; } } }

We start our implementation with the customary validation of the input values. In this specific converter, we are expecting two values of type int, and so we use C# 6.0 Pattern Matching to verify that before continuing. If valid, we compare our two pre-cast values, returning the appropriate string-based direction arrow, dependent on the result of the comparison.

As the ConvertBack method is not required for our example, we simply return an object array that contains two DependencyProperty.UnsetValue values. Let's take a quick look at our View Model next:

using System.Collections.Generic; 
 
namespace CompanyName.ApplicationName.ViewModels 
{ 
  public class WeightMeasurementsViewModel : BaseViewModel 
  { 
    private List<int> weights = 
      new List<int>() { 90, 89, 92, 91, 94, 95, 98, 99, 101 }; 
 
    public List<int> Weights 
    { 
      get { return weights; } 
      set { weights = value; NotifyPropertyChanged(); } 
    } 
  } 
} 

Here, we have a very simple View Model, with just one field and property pair. We've just hardcoded a few test values to demonstrate with. Let's now take a look at our View:

<UserControl 
  x:Class="CompanyName.ApplicationName.Views.WeightMeasurementsView" 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:Converters="clr-namespace:CompanyName.ApplicationName.Converters; 
    assembly=CompanyName.ApplicationName.Converters" 
  xmlns:System="clr-namespace:System;assembly=mscorlib"> 
  <UserControl.Resources> 
    <Converters:HigherLowerConverter x:Key="HigherLowerConverter" /> 
  </UserControl.Resources> 
  <Border BorderBrush="Black" BorderThickness="1" CornerRadius="5" 
    HorizontalAlignment="Left" VerticalAlignment="Top"> 
    <ItemsControl ItemsSource="{Binding Weights}" Margin="20,20,0,20"> 
      <ItemsControl.ItemsPanel> 
        <ItemsPanelTemplate> 
          <StackPanel Orientation="Horizontal" /> 
        </ItemsPanelTemplate> 
      </ItemsControl.ItemsPanel> 
      <ItemsControl.ItemTemplate> 
        <DataTemplate DataType="{x:Type System:Int32}"> 
          <StackPanel Margin="0,0,20,0"> 
            <TextBlock Text="{Binding}" /> 
            <TextBlock HorizontalAlignment="Center"> 
              <TextBlock.Text> 
                <MultiBinding 
                  Converter="{StaticResource HigherLowerConverter}"> 
                  <Binding /> 
                  <Binding 
                    RelativeSource="{RelativeSource PreviousData}" /> 
                </MultiBinding> 
              </TextBlock.Text> 
            </TextBlock> 
          </StackPanel> 
        </DataTemplate> 
      </ItemsControl.ItemTemplate> 
    </ItemsControl> 
  </Border> 
</UserControl> 

After the Converters XAML namespace prefix and the declared HigherLowerConverter element in the Resources section, we have a bordered ItemsControl that is data bound to the Weights property of the View Model that is set as the DataContext of this View. Next, we see a horizontal StackPanel element being used as the ItemsPanelTemplate in the ItemsControl.ItemsPanel property. This simply makes the collection control display items horizontally instead of vertically.

Note that in the following DataTemplate object, we need to specify the data type and so need to import the System namespace from the mscorlib assembly to reference the Int32 type. The binding to the Text property in the first TextBlock specifies that it is binding to the whole data source object, which is simply an integer in this case.

The binding to the Text property in the second TextBlock is where we are using our MultiBinding and IMultiValueConverter elements. We set our HigherLowerConverter class to the Converter property of the MultiBinding object and inside this, we specify two Binding objects. The first is again binding to the integer value and the second uses the RelativeSource.PreviousData property to data bind to the previous integer value. Let's now see the output of this example:

Each value after the first have an arrow displayed underneath, that specifies whether it is higher or lower than the previous value. While the visual output of this example could be improved, it does still highlight the worrying trend of the weight measurements continually increasing towards the end of the sample data. This useful technique can be used in any situation when we need to compare current data values with previous values, such as when displaying share prices, or stock levels.