Friday, February 5, 2021

Multibinding in a XamDataGrid

 A colleague of mine has a need to display a field in an Infragistics XamDataGrid that concatenates two other fields. He is binding to an ObservableCollection and wants the know the 'best' way to do this. I came up with three solutions.

1. Add a readonly property to the collection.
2. Write a multi-value converter and persuade the XamDataGrid to use it
3. Use a template field and a stackpanel

As usual, everything that isn't easy with an Infragistics control is very difficult.

Let's start by looking at solution 1. All I did was add a read-only property to the collection so that it looks like this.

    public class cResult
    {
        public  string Warehouse { get; set; }
        public string StockNumber { get; set; }
        public string WarehouseStock
        {
            get { return string.Format("{0}-{1}", Warehouse, StockNumber); }
        }
    }

And here is the XAML.

<igDP:TextField Label="Warehouse/Stock #" Name="WarehouseStock" Width="auto" />

But it seems much more fun to me to write a StringFormat multivalue converter and use that. Well, it would be if it wasn't such a pain to persuade the XamDataGrid to use it.

Here is the converter...

    public class StringFormatConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            return string.Format(parameter.ToString(), values);
        }
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

And there is the XAML, quite a mouthful, isn't it?

<igDP:TextField Label="Warehouse/Stock #" BindingType="Unbound" EditAsType="sys:String" Width="auto">
    <igDP:TextField.Settings>
        <igDP:FieldSettings>
            <igDP:FieldSettings.EditorStyle>
                <Style TargetType="igEditors:XamTextEditor">
                    <Setter Property="Value">
                        <Setter.Value>
                            <MultiBinding Converter="{StaticResource StringFormatConverter}" ConverterParameter='{}{0}-{1}'>
                                <Binding Path="DataItem.Warehouse"/>
                                <Binding Path="DataItem.StockNumber"/>
                            </MultiBinding>
                        </Setter.Value>
                    </Setter>
                </Style>
            </igDP:FieldSettings.EditorStyle>
        </igDP:FieldSettings>
    </igDP:TextField.Settings>
</igDP:TextField>

Option 3 (template field) might be the most flexible if you don't need complex formatting . It certainly doesn't require as much XAML. Note the use of AlternativeBinding - that was the tricky part to figure out. Also note the use of TextBlock which has a smaller memory and CPU footprint than Label.

<igDP:TemplateField Label="Warehouse/Stock #" BindingType="UseAlternateBinding" AlternateBinding="{Binding}" Width="auto">
    <igDP:TemplateField.DisplayTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Warehouse}"/>
                <TextBlock Text="-"/>
                <TextBlock Text="{Binding StockNumber}"/>
            </StackPanel>
        </DataTemplate>
    </igDP:TemplateField.DisplayTemplate>
</igDP:TemplateField>

Here is a full example showing the two techniques side-by-side.

<Window x:Class="XamDataGridMultiBind.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:XamDataGridMultiBind"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:igDP="http://infragistics.com/DataPresenter"
        xmlns:ig="http://schemas.infragistics.com/xaml"
        xmlns:igEditors="http://infragistics.com/Editors"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <local:StringFormatConverter x:Key="StringFormatConverter" />
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <igDP:XamDataGrid Theme="LunaSilver" DataSource="{Binding Results}" Grid.Column="0" GroupByAreaLocation="None">
            <igDP:XamDataGrid.FieldLayoutSettings>
                <igDP:FieldLayoutSettings SelectionTypeRecord="Single" AutoGenerateFields="False" AllowFieldMoving="No"/>
            </igDP:XamDataGrid.FieldLayoutSettings>
            <igDP:XamDataGrid.FieldSettings>
                <igDP:FieldSettings AllowSummaries="False" SummaryUIType="SingleSelect" LabelTextAlignment="Center" AllowEdit="False"/>
            </igDP:XamDataGrid.FieldSettings>
            <igDP:XamDataGrid.FieldLayouts>
                <igDP:FieldLayout Description="Results">
                    <igDP:FieldLayout.Fields>
                        <igDP:TextField Label="Warehouse" Name="Warehouse" Width="auto"/>
                        <igDP:TextField Label="Stock #" Name="StockNumber" Width="auto"/>
                        <igDP:TextField Label="Warehouse/Stock #" BindingType="Unbound" EditAsType="sys:String" Width="auto">
                            <igDP:TextField.Settings>
                                <igDP:FieldSettings>
                                    <igDP:FieldSettings.EditorStyle>
                                        <Style TargetType="igEditors:XamTextEditor">
                                            <Setter Property="Value">
                                                <Setter.Value>
                                                    <MultiBinding Converter="{StaticResource StringFormatConverter}" ConverterParameter='{}{0}-{1}'>
                                                        <Binding Path="DataItem.Warehouse"/>
                                                        <Binding Path="DataItem.StockNumber"/>
                                                    </MultiBinding>
                                                </Setter.Value>
                                            </Setter>
                                        </Style>
                                    </igDP:FieldSettings.EditorStyle>
                                </igDP:FieldSettings>
                            </igDP:TextField.Settings>
                        </igDP:TextField>
                    </igDP:FieldLayout.Fields>
                </igDP:FieldLayout>
            </igDP:XamDataGrid.FieldLayouts>
        </igDP:XamDataGrid>

        <igDP:XamDataGrid Theme="LunaSilver" DataSource="{Binding Results}" Grid.Column="1" GroupByAreaLocation="None">
            <igDP:XamDataGrid.FieldLayoutSettings>
                <igDP:FieldLayoutSettings SelectionTypeRecord="Single" AutoGenerateFields="False" AllowFieldMoving="No"/>
            </igDP:XamDataGrid.FieldLayoutSettings>
            <igDP:XamDataGrid.FieldSettings>
                <igDP:FieldSettings AllowSummaries="False" SummaryUIType="SingleSelect" LabelTextAlignment="Center" AllowEdit="False"/>
            </igDP:XamDataGrid.FieldSettings>
            <igDP:XamDataGrid.FieldLayouts>
                <igDP:FieldLayout Description="Results">
                    <igDP:FieldLayout.Fields>
                        <igDP:TextField Label="Warehouse" Name="Warehouse" Width="auto"/>
                        <igDP:TextField Label="Stock #" Name="StockNumber" Width="auto"/>
                        <igDP:TextField Label="Warehouse/Stock #" Name="WarehouseStock" Width="auto" />
                    </igDP:FieldLayout.Fields>
                </igDP:FieldLayout>
            </igDP:XamDataGrid.FieldLayouts>
        </igDP:XamDataGrid>

        <igDP:XamDataGrid Theme="LunaSilver" DataSource="{Binding Results}" Grid.Column="2" GroupByAreaLocation="None">
            <igDP:XamDataGrid.FieldLayoutSettings>
                <igDP:FieldLayoutSettings SelectionTypeRecord="Single" AutoGenerateFields="False" AllowFieldMoving="No"/>
            </igDP:XamDataGrid.FieldLayoutSettings>
            <igDP:XamDataGrid.FieldSettings>
                <igDP:FieldSettings AllowSummaries="False" SummaryUIType="SingleSelect" LabelTextAlignment="Center" AllowEdit="False"/>
            </igDP:XamDataGrid.FieldSettings>
            <igDP:XamDataGrid.FieldLayouts>
                <igDP:FieldLayout Description="Results">
                    <igDP:FieldLayout.Fields>
                        <igDP:TextField Label="Warehouse" Name="Warehouse" Width="auto"/>
                        <igDP:TextField Label="Stock #" Name="StockNumber" Width="auto"/>
                        <igDP:TemplateField Label="Warehouse/Stock #" BindingType="UseAlternateBinding" AlternateBinding="{Binding}" Width="auto">
                            <igDP:TemplateField.DisplayTemplate>
                                <DataTemplate>
                                    <StackPanel Orientation="Horizontal">
                                        <TextBlock Text="{Binding Warehouse}"/>
                                        <TextBlock Text="-"/>
                                        <TextBlock Text="{Binding StockNumber}"/>
                                    </StackPanel>
                                </DataTemplate>
                            </igDP:TemplateField.DisplayTemplate>
                        </igDP:TemplateField>
                    </igDP:FieldLayout.Fields>
                </igDP:FieldLayout>
            </igDP:XamDataGrid.FieldLayouts>
        </igDP:XamDataGrid>
    </Grid>
</Window>


--------------------------------------------------------------------------------------

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Data;
namespace XamDataGridMultiBind
{
    public class cResult
    {
        public  string Warehouse { get; set; }
        public string StockNumber { get; set; }
        public string WarehouseStock
        {
            get { return string.Format("{0}-{1}", Warehouse, StockNumber); }
        }
    }
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private ObservableCollection<cResult> _Results = new ObservableCollection<cResult>()  { 
            new cResult() { Warehouse = "Warehouse 1", StockNumber = "Stock Number 1" }, 
            new cResult() { Warehouse = "Warehouse 2", StockNumber = "Stock Number 2" } 
        };
        public ObservableCollection<cResult> Results
        {
            get { return _Results; }
            set { SetProperty(ref _Results, value); }
        }
        public MainWindow()
        {
            InitializeComponent();
        }
        public event PropertyChangedEventHandler PropertyChanged;
        public void SetProperty<T>(ref T storage, T value, [CallerMemberName] string name = "")
        {
            if (!Object.Equals(storage, value))
            {
                storage = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
    }
    public class StringFormatConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            return string.Format(parameter.ToString(), values);
        }
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}