What to do when BindingNavigator Raises Exception on AddNew

I got a great question from a reader recently. It’s essence reads like this:

If I set up drag and drop data binding to a table that has non-nullable columns, and then press the Add New button twice in the BindingNavigator, I get an unhandled exception on the thread. Since all of the code involved in that call chain is in .NET code and assemblies, how can I handle the exception to keep it from blowing up my app?

If you are not already familiar, to get to this point, you have to create a data bound UI using the Data Sources window, or by hooking up the controls manually. What you end up with after dragging a collection from the Data Sources window onto a form is:

  • A DataGridView or Details form of individual controls

  • A BindingSource component that is set as the data source of the grid or the individual controls

  • A BindingNavigator control that is hooked up to the BindingSource component.

If your data source is a typed data set in the same project, you also get a table adapter instance and data set instance as members on the form, and a Form.Load event handler that fills the appropriate table of the data set so that the app functions without any hand written code. If your data source is coming from a different assembly (an Object data source), then it will be up to you to go retrieve an instance of the collection type and set it as the DataSource property on the BindingSource at runtime to complete the data binding chain.

The way the BindingNavigator gets hooked up, it just points to the BindingSource component and uses the API exposed by a BindingSource to navigate forward and back and to add and delete items from the underlying collection. When you press the Add New button on the BindingNavigator, it calls the AddNew method on BindingSource. The BindingSource passes the call to the underlying collection if it implements the IBindingList interface. Calling AddNew usually also implicitly calls EndEdit on the current item if that item type implements the IEditableObject interface, depending on the collection type’s implementation of the AddNew method.

So when dealing with a data table as your collection, you are actually bound to its default DataView. The DataView class implements the IBindingList interface, and the DataRowView class (the items in the collection) implement IEditableObject. When a column in the table is set up so that it does not accept null values, the DataRowView implementation of EndEdit will throw and exception when EndEdit is called if the non-nullable columns have not been provided a value.

The call chain that sets all this up for a standard data set based application is that the BindingNavigator calls into the BindingSource and calls AddNew. This calls into the DataView and adds a new row to the table and starts an editing transaction by calling BeginEdit on the row. When you press the AddNew button a second time, EndEdit is called on the first row you added, which, if you haven’t filled in the non-nullable columns, will throw an exception. Since the call chain goes from BindingNavigator to BindingSource to DataView to DataRowView, there is no user code in the call chain where you can logically insert an exception handler.

You could handle the situation in a crude form by having an Application.ThreadException handler, which will catch all unhandled exceptions on the thread. However, this doesn’t get called until the stack has unraveled all the way back out to the base of the call stack, so it is a little late to be dealing with the exception in a recoverable way.

A better solution is to inherit from the BindingSource component and provide your own implementation to AddNew. The following implementation (thanks to Steve Lasker and Daniel Herling on the product team in Redmond for coming up with this) shows how:

privateclassMyBindingSource : BindingSource

{

public MyBindingSource()

: base()

{

}



publicoverrideobject AddNew()

{

object o = null;

try

{

o = base.AddNew();

}

catch (System.Exception ex)

{

this.OnDataError(

newBindingManagerDataErrorEventArgs(ex));

}

return o;

}

}

With this in place, you can just handle the DataError event on the BindingSource component to do whatever is appropriate based on the exception.