Multibinding in Universal Windows Apps

February 21st 2016 Universal Apps XAML

There are a lot of features in WPF that are not available in other XAML dialects, such as the latest one for Universal Windows applications. One of them is multibinding, which allows binding of multiple view model properties to a single dependency property of a WPF control. Fortunately, there is a third party library available which makes this possible in UWP apps as well.

WPF Example

Here's a simple example of WPF multibinding in action:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <ListView ItemsSource="{Binding SourceItems}" 
              SelectedItem="{Binding SelectedSourceItem}" 
              Grid.Column="0" Grid.Row="0" />
    <ListView ItemsSource="{Binding DestinationItems}" 
              SelectedItem="{Binding SelectedDestinationItem}" 
              Grid.Column="1" Grid.Row="0" />
    <TextBlock TextAlignment="Center"
               Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2">
        <TextBlock.Text>
            <MultiBinding Converter="{StaticResource StringFormatConverter}" 
                          ConverterParameter="Copy {0} to {1}">
                <Binding Path="SelectedSourceItem" />
                <Binding Path="SelectedDestinationItem" />
            </MultiBinding>
        </TextBlock.Text>
    </TextBlock>
</Grid>

Whenever the selected item changes in any of the lists, the text in the text block will automatically update accordingly. To create a single value from multiple properties to use for binding, an IMultiValueConverter would need to be implemented:

public class StringFormatConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, 
                          object parameter, CultureInfo culture)
    {
        var format = parameter as string;

        if (format == null)
        {
            return null;
        }

        return string.Format(format, values);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, 
                                object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

The resulting solution is very flexible and reusable, but unfortunately UWP has no multibinding support out of the box.

The View Model Approach

The only way to implement a similar functionality without multibinding support would be to move all the logic from the view and the converter to the view model:

public class MainViewModel : ViewModelBase
{
    private string _selectedSourceItem;
    private string _selectedDestinationItem;

    public List<string> SourceItems { get; set; }
    public List<string> DestinationItems { get; set; }

    public string SelectedSourceItem
    {
        get { return _selectedSourceItem; }
        set
        {
            _selectedSourceItem = value;
            RaisePropertyChanged();
            RaisePropertyChanged(() => Description);
        }
    }

    public string SelectedDestinationItem
    {
        get { return _selectedDestinationItem; }
        set
        {
            _selectedDestinationItem = value;
            RaisePropertyChanged();
            RaisePropertyChanged(() => Description);
        }
    }

    public string Description => 
        $"Copy {SelectedSourceItem} to {SelectedDestinationItem}";
}

Not only is this approach more tightly coupled with both the formatting text and its arguments hard coded in the view model; it also puts the burden of correctly raising the PropertyChanged event on the developer. Notice how I raise the event for the Description property inside the setters of SelectedSourceItem and SelectedDestinationItem, because I know the former will change when any of the latter changes. This makes the solution more brittle: if I change the definition of Description to depend on different properties, I must remember to update the calls to RaisePropertyChanged() in the view model accordingly.

Of course, in XAML the control can now simply be bound directly to the new calculated view model property:

<TextBlock Text="{Binding Description}" TextAlignment="Center" 
           Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2"/>

Cimbalino Multibinding Behavior

Although there is no built-in support for multibinding available in UWP, you can install a third party library, which will make the UWP code much more similar to the WPF one. Cimbalino Toolkit contains a specialized multibinding behavior for this very purpose. Here's how the XAML would look like if you take advantage of it:

<TextBlock TextAlignment="Center"
           Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2">
    <interactivity:Interaction.Behaviors>
        <behaviors:MultiBindingBehavior PropertyName="Text" 
                   Converter="{StaticResource StringFormatConverter}" 
                   ConverterParameter="Copy {0} to {1}">
            <behaviors:MultiBindingItem 
                       Value="{Binding SelectedSourceItem}"/>
            <behaviors:MultiBindingItem 
                       Value="{Binding SelectedDestinationItem}"/>
        </behaviors:MultiBindingBehavior>
    </interactivity:Interaction.Behaviors>
</TextBlock>

Yes, the syntax is a bit more verbose, but it should still be familiar enough to anyone with experience in WPF multibinding. The converter is even more similar the the WPF one:

public class StringFormatConverter : MultiValueConverterBase
{
    public override object Convert(object[] values, Type targetType, 
                                   object parameter, CultureInfo culture)
    {
        var format = parameter as string;

        if (format == null)
        {
            return null;
        }

        return string.Format(format, values);
    }

    public override object[] ConvertBack(object value, Type targetType, 
                                         object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Since there's no IMultiValueConverter in UWP, Cimbalino provides MultiValueConverterBase base class instead. The signature is almost identical.

In my opinion, when you have a genuine need for multibinding in UWP, Cimbalino's multibinding behavior is the preferred solution to hard coding everything in the view model.

Copyright
Creative Commons License