Page 1 of 1

MultiBinding (WPF)

#1 andrewsw  Icon User is offline

  • lashings of ginger beer
  • member icon

Reputation: 6340
  • View blog
  • Posts: 25,572
  • Joined: 12-December 12

Posted 24 January 2015 - 06:22 PM

This tutorial demonstrates MultiBinding or a multi-valued converter.

Pre-requisites: You need to understand the single-valued converter first, you are far more likely to use it and it provides a basis for this tutorial. You should also understand WPF binding in general, and How to: Implement Property Change Notification.

The docs for MultiBinding include an example which is very similar to mine. This is to be expected because formatting a full-name is the most common, and natural, example. My example is fuller and discusses a few additional, small, topics.



An multi-valued converter takes an array of objects and returns a single value. For our example a person's first and last-names are entered in TextBoxes and the converter is used to construct a full-name from these, for display in a TextBlock. Without a multi-valued converter a property is typically created in the ViewModel (or Model) to construct the full-name, and a TextBlock bound to this property. For example:
        public string LastName {
            get {
                return _LastName;
            }
            set {
                if (_LastName != value) {
                    _LastName = value;
                    this.FullName = string.Format("{0} {1}", _FirstName, _LastName);
                    onpropertychanged("LastName");
                }
            }
        }

There is nothing wrong with this approach, but it is nice to be able to extract this out into a converter, particularly when the converter is flexible enough to be re-used.

Attached Image

Here is the Person class to provide the FirstName and LastName properties, it is fairly standard.

Person.cs
Spoiler

With thanks to tlhIn`toq this also demonstrates the use of pre-processor directives. The #define directive at the top defines a symbol, stating that NET 4.5 is in use. The #if and #else directives will cause different sections of code to be disabled. With the defined symbol at the top the #else code is disabled and appears in grey in Visual Studio. If the #define line is removed then it is the #if section that is greyed.

[DebuggerStepThrough] is an Attribute, a declarative tag. These are not essential to the example.

Here is the converter, FullNameConverter.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;      // IMultiValueConverter

namespace MultiBinding {
    public class FullNameConverter : IMultiValueConverter {

        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
            StringBuilder sbFullName = new StringBuilder();
            foreach (object name in values) {
                sbFullName.AppendFormat("{0} ", name.ToString());
            }
            return sbFullName.ToString();
        }

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


This is a very small file. When you create it you can point at IMultiValueConverter and VS will offer to create the necessary method-stubs for you.

For the Convert method object[] values contains the collection of values, from which it constructs and returns a single value. In our case, it contains the first and last-names to construct a full-name. object parameter contains an optional parameter.

The use of a StringBuilder is not essential, and overkill for our full-name example, but could be useful if our converter is re-used elsewhere.

The ConvertBack method is not implemented. If the full-name were displayed in a TextBox (rather than a non-editable TextBlock) then it may be possible (but not a good idea) to split the edited full-name at spaces and re-assign the values as first and last-name.



This does indicate an issue with the multi-valued converter. It is not type-safe: the values are just objects. Because of this we should not attempt to distinguish between the different supplied values based on their order. That is, we should not make the assumption that the first value is always the first-name and the second value is always the last-name. (ConvertBack is rarely used with a multi-valued converter.)



Here is the XAML, Mainwindow.xaml:
<Window x:Class="MultiBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Width="400" Height="300" 
        xmlns:mb="clr-namespace:MultiBinding" Loaded="Window_Loaded">
    <window.Resources>
        <mb:FullNameConverter x:Key="fNameConverter" />
    </window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0"
                   Margin="10" Text="Enter Person's Name" />
        <TextBlock Grid.Column="0" Grid.Row="1" Margin="10"
                   Text="First Name" />
        <TextBox Name="txtFirstName" Grid.Column="1" Grid.Row="1" Margin="10" 
                 Text="{Binding Path=FirstName, TargetNullValue='Enter First Name'}" />
        <TextBlock Grid.Column="0" Grid.Row="2" Margin="10" 
                   Text="Last Name" />
        <TextBox Name="txtLastName" Grid.Column="1" Grid.Row="2" Margin="10" 
                 Text="{Binding Path=LastName, TargetNullValue='Enter Last Name'}" />
        <TextBlock Grid.Column="0" Grid.Row="3" Margin="10"
                   Text="Full Name" />
        <TextBlock Grid.Column="1" Grid.Row="3" Margin="10">
            <TextBlock.Text>
                <MultiBinding Converter="{StaticResource fNameConverter}">
                    <Binding ElementName="txtFirstName" Path="Text" />
                    <Binding ElementName="txtLastName" Path="Text" />
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
        
        <Button Name="btnClose" Grid.Row="4" Grid.Column="1"
                Margin="10" Content="Close" Click="btnClose_Click" />
    </Grid>
</Window>


    <window.Resources>
        <mb:FullNameConverter x:Key="fNameConverter" />
    </window.Resources>

Our converter is supplied as a resource to the XAML, and given a name, a Key, so that it can be referred to and used within the XAML.
        <TextBlock Grid.Column="1" Grid.Row="3" Margin="10">
            <TextBlock.Text>
                <MultiBinding Converter="{StaticResource fNameConverter}">
                    <Binding ElementName="txtFirstName" Path="Text" />
                    <Binding ElementName="txtLastName" Path="Text" />
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>

This is how we use the converter, and how we supply the list of values for it.

Here is the code-behind, Mainwindow.xaml.cs, which has very little code. NB I have omitted the default using statements at the top.
namespace MultiBinding {
    /// <summary>
    /// Interaction logic for Mainwindow.xaml
    /// </summary>
    public partial class MainWindow : Window {

        private Person _person;

        public MainWindow() {
            InitializeComponent();

            //_person = new Person { FirstName = "<Enter first name>", LastName = "<Enter last name>" };
            _person = new Person();
            // set the DataContext of the Window
            this.DataContext = _person;
            this.txtLastName.GotFocus += delegate {
                txtLastName.SelectAll();
            };
        }

        private void Window_Loaded(object sender, RoutedEventArgs e) {
            this.txtFirstName.Focus();
            this.txtFirstName.SelectAll();
        }
        private void btnClose_Click(object sender, RoutedEventArgs e) {
            this.Close();
        }
    }
}


            //_person = new Person { FirstName = "<Enter first name>", LastName = "<Enter last name>" };

This would supply default values for the new Person but I decided to demonstrate the use of TargetNullValue instead.
            this.txtLastName.GotFocus += delegate {
                txtLastName.SelectAll();
            };

When tabbing from first to last-name this causes the text of the last-name to be selected. You might notice, however, that if you click into the last-name it momentarily selects the text but immediately collapses the selection. It is possible (with some effort!) to more fully control the tabbing behaviour but I didn't pursue this for this tutorial.
        private void Window_Loaded(object sender, RoutedEventArgs e) {
            this.txtFirstName.Focus();
            this.txtFirstName.SelectAll();
        }

This is useful to know, selecting the first-name text when the Window is loaded.

I hope that you found this tutorial helpful. Here is another interesting example which uses sliders and works out a sum and average. It also demonstrates the use of the optional parameter to the Convert method.

This post has been edited by andrewsw: 24 January 2015 - 06:27 PM


Is This A Good Question/Topic? 1
  • +

Page 1 of 1