Page view counter

It Ain't You, Babe… A Not-a-bug bug in DataGrid

SLLogoWords

 

I'll be writing a set of mini-tutorials on the DataGrid that will, as the King advises, begins at the beginning, goes on till it comes to the end and then stops[1], but before I do, a "Set Of Behaviors That Is Perfectly Understandable But Not At All What The Programmer Expects (SOBTIPUBNAAWTPE)" has surfaced in DataGrid.  (I was told this is not a bug – my response was a bit snide).

Since this is causing a great deal of frustration and confusion, I wanted to alert you to it before you pull all your hair out – I'll also let you know as soon as I have the details of the work around.

iStock_ ice create spilled Large

To understand the bug (oops) you need to understand two properties associated with DataBinding Validation for two-way binding.

In English that means that when you enter data that will be written back to the data source, Silverlight will validate the data and handle two types of exceptions for you if you set the right properties.  The two types of exceptions are:

  1. Exceptions that are thrown when the binding engine tries to convert the type of the data
  2. Exceptions that are thrown from within the binding object's set accessor

The two properties that you need to set are NotifyOnValidationError and ValidatesOnException.  They both default to false. You want to set them to true; if you do the exceptions are turned into BindingValidationError events – and even better they are bubbling events which means that you can put your event handler on the containing control. 

The way this is supposed to work is that you can associate an event handler for the BindingValidationError with the DataGrid itself, and if there is a problem binding the data in any of the columns it will bubble up to that one handler, rather than firing an exception that might bring your application to a stand still. 

As a practical test of this, and stealing borrowing from examples from Reid Maker (who pointed out the SOBTIPUBNAAWTPE in private correspondence) and from Manish Dalal's Blog (who illustrated how this should work during Beta 2) I tried to prove to myself that it wasn't a bug by trying various variations (e.g., moving the event handler in and out of Xaml, moving the exception generation in and out of an event, generating the exception in each of the two ways, and so forth).

Here is the code, somewhat simplified

The Data Source Class

using System;
using System.ComponentModel;

namespace DataGridBindingValidationTester
{
   public class TestData : INotifyPropertyChanged
   {
      public event PropertyChangedEventHandler PropertyChanged;
      private int id = 0;
      private string name = string.Empty;

      private void NotifyChange( String name )
      {
         if ( PropertyChanged != null )
         {
            PropertyChanged( this, new PropertyChangedEventArgs( name ) );
         }
      }

      public string Name
      {
         get { return name; }
         set
         {
            name = value;
            NotifyChange( "Name" );
         }
      }

      public int Id
      {
         get
         {
            return id;
         }
         set
         {
            if ( value == 9 )
            {
               throw new Exception( "can't have 9" );
            }
            id = value;
            NotifyChange( "Id" );
         }
      }
   }
}

Page Xaml

<UserControl x:Class="DataGridBindingValidationTester.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300" xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data">
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="0.15*"/>
            <RowDefinition Height="0.85*"/>
        </Grid.RowDefinitions>
        <TextBlock Margin="18,8,56,8" x:Name="Output" FontSize="14" Text="Error Messages Show Here" TextWrapping="Wrap"/>
        <data:DataGrid x:Name="TestDataGrid" Margin="10,10,10,10" Grid.Row="1"  AutoGenerateColumns="False" >
            <data:DataGrid.Columns>
                <data:DataGridTextColumn Header="Name" Binding="{Binding Name}"  />
                <data:DataGridTemplateColumn Header="ID">
                    <data:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock  Text="{Binding Id}"  />
                        </DataTemplate>
                     </data:DataGridTemplateColumn.CellTemplate>
                    <data:DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <TextBox Text="{Binding Id,Mode=TwoWay,NotifyOnValidationError=true,ValidatesOnExceptions=true}" />
                        </DataTemplate>
                    </data:DataGridTemplateColumn.CellEditingTemplate>
                </data:DataGridTemplateColumn>
            </data:DataGrid.Columns>
        </data:DataGrid>
    </Grid>
</UserControl>

Page.xaml.cs

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace DataGridBindingValidationTester
{
   public partial class Page : UserControl
   {
      public Page()
      {
         InitializeComponent();
         Loaded += new RoutedEventHandler( Page_Loaded );
         TestDataGrid.BindingValidationError += 
            new EventHandler<ValidationErrorEventArgs>( TestDataGrid_BindingValidationError );
      }

      void TestDataGrid_BindingValidationError( object sender, ValidationErrorEventArgs e )
      {
         if ( e.Action == ValidationErrorEventAction.Added )
         {
            ( (Control) e.OriginalSource ).Background = new SolidColorBrush( Colors.Red );
            Output.Text = "Error: " + e.Error.Exception.Message;
         }
         else if ( e.Action == ValidationErrorEventAction.Removed )
         {
            ( (Control) e.OriginalSource ).Background = new SolidColorBrush( Colors.White );
         }

      }

      void Page_Loaded( object sender, RoutedEventArgs e )
      {
         List<TestData> tests = new List<TestData>();

         tests.Add( new TestData { Id = 1, Name = "A" } );
         tests.Add( new TestData { Id = 4, Name = "B" } );
         tests.Add( new TestData { Id = 2, Name = "C" } );
         tests.Add( new TestData { Id = 5, Name = "D" } );
         tests.Add( new TestData { Id = 3, Name = "E" } );
         tests.Add( new TestData { Id = 8, Name = "F" } );
         tests.Add( new TestData { Id = 7, Name = "G" } );
         TestDataGrid.ItemsSource = tests;
         
      }
   }
}

The key to why this should work is that the DataGrid's Cell Editing Template for Binding the ID (when the user is entering data) has a DataTemplate that uses a TextBox that is bound with both of the necessary properties,

<DataTemplate>
    <TextBox Text="{Binding Id,
                    Mode=TwoWay,
                    NotifyOnValidationError=true,
                    ValidatesOnExceptions=true}" />
</DataTemplate>

In addition, the event handler is created in the class constructor and implemented in the code behind.

TestDataGrid.BindingValidationError += 
   new EventHandler<ValidationErrorEventArgs>( TestDataGrid_BindingValidationError );

void TestDataGrid_BindingValidationError( object sender, ValidationErrorEventArgs e )
{
   if ( e.Action == ValidationErrorEventAction.Added )
   {
      ( (Control) e.OriginalSource ).Background = new SolidColorBrush( Colors.Red );
      Output.Text = "Error: " + e.Error.Exception.Message;
   }
   else if ( e.Action == ValidationErrorEventAction.Removed )
   {
      ( (Control) e.OriginalSource ).Background = new SolidColorBrush( Colors.White );
   }

}

The bug is that this does not work.  The reason this is not a bug but a SUBTIPUBNAAWTPE is this: what is actually causing this to fail is not that the event wouldn't be raised, but rather that by the time the error might be raised, the DataGrid has already shifted back from TextBox to TextBlock – and the properties are no longer set.

(Okay, my snide answer was this: …it may not be a BindingValidation bug, but it is a Silverlight bug by any reasonable definition; or it is like saying that it isn’t a bug when your brakes fail because they were never really designed to stop the car if the car is on an actual road, though they work just great up  on the lift.  )

More about this soon…. including a much more systematic review of DataGrid.

[1] Alice in Wonderland – Public Domain – Project Gutenberg

Published 22 October 2008 08:48 AM by jesseliberty
Filed under:

Comments

# Steve Strong said on 22 October, 2008 09:11 AM

Thank you for removing the line numbers in you posting.  this makes it a lot easier to verify your results.

# wisecarver said on 22 October, 2008 09:28 AM

Note: IE8.B2 will only show the code_block vertical scrollbars in compatibility mode.

# wackyphill said on 22 October, 2008 10:37 AM

Jesse,

Thanks for writing about this. It has been driving me crazy!

The datagrid also seems to have some rendering problems where it does not redraw all of its rows correctly in certain situations.

Like when a button in your grid fires an event and then the user sorts by a column...very frustrating.

Please tell me the DataGrid people are busy working to improve this very valuable control.

BTW if/when an update tot he DataGrid happens how will we get it and start using it instead of the existing one?

# CoderX said on 22 October, 2008 11:48 AM

wackyphill- We've been seeing the same thing. There is a thread going in the forums, but so far no response at all from MS. Jesse if you could make sure the right people see this we would appreciate it. I'm supposed to ship in a little over a week and this bug is giving me ulcers. silverlight.net/.../36559.aspx

# 2008 October 23 - Links for today « My (almost) Daily Links said on 23 October, 2008 05:08 AM

Pingback from  2008 October 23 - Links for today &laquo; My (almost) Daily Links

# Community Blogs said on 23 October, 2008 03:30 PM

In this issue: Tim Heuer, Jeff Prosise, Jeff Weber, and Jesse Liberty. From SilverlightCream.com : Silverlight

# esh said on 24 October, 2008 05:14 AM

I am also waiting for removed editing datagrid events, which where there in Beta2. Hopefully DataGrid will be shipped soon as a patch somehow ;)...

# twinae said on 24 November, 2008 06:50 AM

I don't know if you solved the problem, but i found something reading some blogs and trying some examples:

<DataTemplate>

   <TextBox BindingValidationError="TestDataGrid_BindingValidationError"

        Text="{Binding Id, Mode=TwoWay, NotifyOnValidationError=true,ValidatesOnExceptions=true}" />

</DataTemplate>

If we add the handler to the textbox like you can see above everything works perfect.

Hope this help.

# spiderman110 said on 16 December, 2008 10:42 PM

In order to receive notification that a validation error has occurred, you must tell the binding engine that you wish to receive notifications by setting both the ValidatesOnExceptions and NotifyOnValidationError properties to true on the binding object. Setting ValidatesOnExceptions to true tells the binding engine to create a validation error when an exception occurs. Setting NotifyOnValidationError to true tells the binding engine to raise the BindingValidationError event when a validation error occurs and resolves.

Create an event handler on the target object or <br>any of its parents</br> to handle the BindingValidationError event. The BindingValidationError event is a routed event, so if you do not handle it on the element that raised the event, it will continue to bubble up until it is handled. For more information on routed events, see Events and Delegates

I find this from Silverlight SDK.

It looks like a bug in datagrid.

# nicos said on 28 May, 2009 10:11 AM

Its now 5 months later and I'm using Silverlight Toolkit March 2009 and this problem seems to be there still.  Was it fixed it the mean time and am I missing something ?