DataGrid multibinding column with async data source shows incorrect data

Jul 2, 2010 at 10:50 AM

I have a weird problem with a multi bound column that shall display a current and a limit value.
It shall be possible to edit the limit which is sent to the server.

Everything works fine for a 'synchronous' data source but when the applying this to the client/server object it starts to fail.

Also the DataGrid column needs UpdateSourceTrigger to be LostFocus.

I think it's easiest to explain with the attached example code.
Run it and update the value for the MultiBound to something new, on the format "x:y" (the colon is needed)
Then don't change row, click with the mouse or tab/shift tab to remain on the same row.
You will see that quite frequently the second part of the multi value is the old, not updated though the source have been updated.

How can I resolve this?? The problem get's there due to LostFocus and the async data source but I think this shall be ok.
regards and tia
 /Stefan

Very simple example code here which shows the problem:

---------- MainWindow.xaml ----------
<Window x:Class="WpfMiniMultiBindGridCol.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:src="clr-namespace:WpfMiniMultiBindGridCol"
        Title="MainWindow" Height="298" Width="525">

    <Window.Resources>
        <src:AMultiConverter x:Key="multiconverter"/>
    </Window.Resources>
    <Grid>
        <DataGrid AutoGenerateColumns="False" Height="200" HorizontalAlignment="Left" Margin="21,12,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="429">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Empty" />
                <DataGridTextColumn Header="MultiBound" >
                    <DataGridTextColumn.Binding>
                        <MultiBinding Converter="{StaticResource multiconverter}" UpdateSourceTrigger="LostFocus">
                            <Binding Path="Str1" Mode="OneWay"/>
                            <Binding Path="Str2"/>
                        </MultiBinding>
                    </DataGridTextColumn.Binding>
                </DataGridTextColumn>
                <DataGridTextColumn Header="SingleBound" >
                    <DataGridTextColumn.Binding>
                        <Binding Path="Str2"/>
                    </DataGridTextColumn.Binding>
                </DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>
---------- MainWindow.xaml.cs ----------
using System.Collections.ObjectModel;
using System.Windows;

namespace WpfMiniMultiBindGridCol
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            ObservableCollection<ADataObject> data = new ObservableCollection<ADataObject>();
            data.Add(new ADataObject("A", "B"));
            data.Add(new ADataObject("c", "d"));
            dataGrid1.ItemsSource = data;
        }
    }
}
---------- ConverterAndObject.cs ----------
using System;
using System.Windows.Data;
using System.ComponentModel;

namespace WpfMiniMultiBindGridCol
{
    class AMultiConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            object obj;

            if (values[0] == System.Windows.DependencyProperty.UnsetValue || values[1] == System.Windows.DependencyProperty.UnsetValue)
                obj = System.Windows.DependencyProperty.UnsetValue;
            else
                obj = values[0] + ":" + values[1];

            System.Diagnostics.Trace.TraceInformation("Convert: " + obj + ". " + values[0].GetType() + ", " + values[1].GetType());
            return obj;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            object[] obj;
            string[] both = ((string)value).Split(':');

            if (both.Length == 2)
                obj = new object[] { Binding.DoNothing, both[1] };
            else
                obj = new object[] { Binding.DoNothing, System.Windows.DependencyProperty.UnsetValue };

            System.Diagnostics.Trace.TraceInformation("ConvertBack: " + obj[0] + ", " + obj[1] + ". " + value);
            return obj;
        }
    }


    class ADataObject : INotifyPropertyChanged
    {
        public ADataObject(string s1, string s2)
        {
            Str1 = s1;
            str2 = s2;
        }

        public string Str1 { get; set; }

        private string str2;
        public string Str2
        {
            get { return str2; }
            set
            {
                // delay setting of this to simulate server communication
                System.Threading.Thread t = new System.Threading.Thread(delegate(object o)
                {
                    string s = (string)o;
                    str2 = s;
                    if (PropertyChanged != null)
                        PropertyChanged(this, new PropertyChangedEventArgs("Str2"));
                });
                t.Start(value);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}