This project has moved. For the latest updates, please go here.

Multiple UpdateBindingSource calls with DataGrid

Apr 22, 2015 at 4:27 PM
Hi guys,

I have an object which implements the IDataErrorInfo and IValidatableObject interfaces. When the indexer for the property 'Quantity' of this object gets called it calls a validation method that fetches some value from the database to check it against the entered value. I have noticed that when I edit the 'Quantity' property, the indexer is being called multiple times because of calls to the DataGrid.Cell.UpdateContentBindingTarget method. Specifically, when I enter a value that
makes the validation method succeed, the indexer is called 3 times but when I enter a value that makes the validation method fail, the indexer is being called 13 times.

Below, is the simplified xaml and the stack trace of these calls. Why is this happening and is there a way to prevent all these data binding updates?
<xcdg:DataGridControl AutoCreateColumns="False" IsDeleteCommandEnabled="False" IsRefreshCommandEnabled="False" ItemScrollingBehavior="Immediate" ItemsSource="{Binding Path=OrderDetails}">
   <xcdg:Column FieldName="Quantity" CellEditorDisplayConditions="CellIsCurrent" CellHorizontalContentAlignment="Right" Title="Quantity">
      <xcdg:Column.CellContentTemplate>
         <DataTemplate>
            <TextBlock Text="{Binding Path=., StringFormat={}{0:N0}, ValidatesOnDataErrors=True}" />
         </DataTemplate>
      </xcdg:Column.CellContentTemplate>
      <xcdg:Column.CellErrorStyle>
         <Style TargetType="{x:Type xcdg:Cell}" >
            <Setter Property="BorderBrush" Value="Red" />
            <Setter Property="BorderThickness" Value="1" />
            <Style.Triggers>
               <Trigger Property="Validation.HasError" Value="True">
                  <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}" />
               </Trigger>
            </Style.Triggers>
         </Style>
      </xcdg:Column.CellErrorStyle>
      <xcdg:Column.CellEditor>
         <xcdg:CellEditor>
            <xcdg:CellEditor.EditTemplate>
               <DataTemplate>
                  <xceed:IntegerUpDown Value="{xcdg:CellEditorBinding}" Minimum="0" />
               </DataTemplate>
            </xcdg:CellEditor.EditTemplate>
         </xcdg:CellEditor>
      </xcdg:Column.CellEditor>
   </xcdg:Column>
</xcdg:DataGridControl>
-- StackTrace when the validation method succeeds:
this sequence is called 2 times
DataGrid.Cell.UpdateContentBindingSource()
DataGrid.Cell.ValidateAndSetAllErrors()
DataGrid.Cell.EndEdit()
DataGrid.Row.EndEditCore()
DataGrid.DataRow.EndEditCore()
DataGrid.Row.EndEdit()
DataGrid.DataGridControl.OnPreviewMouseDown()

and then this sequence is called 1 time for a total of 3 calls to my object's indexer.
DataGrid.Cell.UpdateContentBindingTarget()
DataGrid.Row.UpdateCellsContentBindingTarget()
DataGrid.Row.TerminateEditionFromEndEdit()
DataGrid.Row.EndEdit()
DataGrid.DataGridControl.OnPreviewMouseDown()

--- StackTrace when the validation method fails:
this sequence is called 2 times
DataGrid.Cell.UpdateContentBindingSource()
DataGrid.Cell.ValidateAndSetAllErrors()
DataGrid.Cell.EndEdit()
DataGrid.Row.EndEditCore()
DataGrid.DataRow.EndEditCore()
DataGrid.Row.EndEdit()
DataGrid.DataGridControl.OnPreviewMouseDown()

then this sequence is called 6 times
DataGrid.Cell.UpdateContentBindingSource()
DataGrid.Cell.ValidateAndSetAllErrors()
DataGrid.Cell.CascadeValidation()
DataGrid.Cell.ValidateAndSetAllErrors()
DataGrid.Row.EndEditCore()
DataGrid.DataRow.EndEditCore()
DataGrid.Row.EndEdit()
DataGrid.DataGridControl.OnPreviewMouseDown()

then this sequence is called 4 times
DataGrid.Cell.UpdateContentBindingSource()
DataGrid.Cell.ValidateAndSetAllErrors()
DataGrid.Cell.CascadeValidation()
DataGrid.Cell.ValidateAndSetAllErrors()
DataGrid.Cell.ContentBinding_TargetUpdated()

and finally this sequence is called 1 times for a total of 13 calls to my object's indexer.
DataGrid.Cell.UpdateContentBindingTarget()
DataGrid.Row.UpdateCellsContentBindingTarget()
DataGrid.Row.TerminateEditionFromEndEdit()
DataGrid.Row.EndEdit()
DataGrid.DataGridControl.OnPreviewMouseDown()
Apr 24, 2015 at 3:45 PM
Hi people,

Can somebody please acknowledge that this is being verified or not?

Thanks,
Developer
Apr 24, 2015 at 8:17 PM
Hi,

First thing I can see : In you XAML, the "xcdg:Column" should be included in a "xcdg:DataGridControl.Columns" tag.

After modifying slightly your sample, I have 2 calls to "UpdateSource" and 1 call to "UpdateTarget" wheather there is an error or not.

Before going further in the code, can you check my sample does what you want or if I am missing something through my sample that could explain the 13 calls.

Thanks.
<xcdg:DataGridControl x:Name="_dataGrid"
                            AutoCreateColumns="False"
                            IsDeleteCommandEnabled="False"
                            IsRefreshCommandEnabled="False"
                            ItemScrollingBehavior="Immediate"
                            ItemsSource="{Binding Path=OrderDetails}">
         <xcdg:DataGridControl.Columns>
            <xcdg:Column FieldName="Quantity"
                         CellEditorDisplayConditions="CellIsCurrent"
                         CellHorizontalContentAlignment="Right"
                         Title="Quantity">
               <xcdg:Column.CellContentTemplate>
                  <DataTemplate>
                     <TextBlock Text="{Binding Path=., StringFormat={}{0:N0}, ValidatesOnDataErrors=True}" />
                  </DataTemplate>
               </xcdg:Column.CellContentTemplate>
               <xcdg:Column.CellErrorStyle>
                  <Style TargetType="{x:Type xcdg:Cell}">
                     <Setter Property="BorderBrush"
                             Value="Red" />
                     <Setter Property="BorderThickness"
                             Value="1" />
                  </Style>
               </xcdg:Column.CellErrorStyle>
               <xcdg:Column.CellEditor>
                  <xcdg:CellEditor>
                     <xcdg:CellEditor.EditTemplate>
                        <DataTemplate>
                           <toolkit:IntegerUpDown Value="{xcdg:CellEditorBinding}"
                                                  Minimum="0" />
                        </DataTemplate>
                     </xcdg:CellEditor.EditTemplate>
                  </xcdg:CellEditor>
               </xcdg:Column.CellEditor>
            </xcdg:Column>
         </xcdg:DataGridControl.Columns>
         
</xcdg:DataGridControl>

public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();

      _dataGrid.DataContext = this;
      this.OrderDetails = new ObservableCollection<OrderDetail>()
      {
        new OrderDetail() { Name = "Orange", Quantity = 12},
        new OrderDetail() { Name = "Apple", Quantity = 2},
        new OrderDetail() { Name = "Tomato", Quantity = 33},
        new OrderDetail() { Name = "Strawberry", Quantity = 10},
        new OrderDetail() { Name = "Peas", Quantity = 15}
      };
    }

    public ObservableCollection<OrderDetail> OrderDetails
    {
      get;
      set;
    }
  }

  public class OrderDetail : IDataErrorInfo, INotifyPropertyChanged
  {
    public string Name
    {
      get;
      set;
    }

    public int Quantity
    {
      get
      {
        return quantity;
      }
      set
      {
        quantity = value;
        this.NotifyPropertyChanged( "Quantity" ); 
      }
    }
    private int quantity;

    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged( string propertyName )
    {
      if( this.PropertyChanged != null )
      {
        this.PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
      }
    }

    public string Error
    {
      get
      {
        return string.Empty;
      }
    }

    public string this[ string columnName ]
    {
      get
      {
        string result = null;
        switch( columnName )
        {
          case "Quantity":
            {
              if( this.Quantity <= 5 )
              {
                result = "Value must be greater than 5";
              }
              break;
            }
          default:
            {
              break;
            }
        }
        return result;
      }
    }
  }
Apr 26, 2015 at 1:15 PM
Hi,

I have toyed a little bit with your sample and when I added a column for the "Name" property, I started to see some more calls to the indexer for the "Quantity" column. When I added yet another property and a corresponding column, there was even more indexer calls for " Quantity". Like I said in my original post, the code that I supplied was a simplified version of the real code that in fact contains 7 columns. So it would seem that the more columns, the more indexer calls are made.

Thanks for looking into that.
Developer
Apr 29, 2015 at 2:24 PM
Hi MichelK7,

After looking in the DataGrid code, it appears to be a normal behavior to refresh the binding before pushing the value to the user object when a cell is done editing. The reason why the calls to DataGrid.Cell.UpdateContentBindingTarget and DataGrid.Cell.UpdateContentBindingSource are multiplied with many columns is that a column might depends on the value of another column.

So if the Quantity of Strawberry is edited to a value below 5, we validate the "Quantity" column for this row. But we also needs to validate the "Name" colum" to check for errors that might happens on the "Name" column if the "Quantity" column doesn't respect a specific validation. At the end, each cell of a row will be validated when 1 cell in that row is done edited.

As you pointed out, the UpdateTarget and UpdateSource method will call your indexer. I suggest you don't fetch slow data too often in that call or cache the value for future indexer calls.

Thanks.
Apr 29, 2015 at 3:26 PM
Good morning Mr. Boucher,

Thanks for the reply. The behaviour you explained is more or less what I was 'afraid' of. In my present case, I don't need this checking for errors in other columns. By the way, when I do need this type of checking among related properties in my objects, what I do is, on the setter of property 'XYZ' I raise an PropertyChanged event for property 'ABC' so that the indexer gets called for that property too. Anyways it might be useful to have the capability to opt-out of this behaviour via a dependency property of the DataGrid.

Again, thanks for looking into this.