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

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in z Silverlight Archives. Bookmark the permalink.