0

In my Windows Forms project I have a BindingNavigator control in a user control (It is a data-binding scenario). Pressing one of the buttons, First, Previous, Next or Last throws exception, crashing the application entirely. To tackle this I have written following code to handle the exception, in the user control.

protected override void OnLoad(EventArgs e)
{
    Application.ThreadException += Application_ThreadException;
    base.OnLoad(e);
}

private void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
    if (bindingNavigatorMovePreviousItem.Pressed)
    {
        MessageBox.Show(e.Exception.Message);
        bsRestrictions.CancelEdit();
    }
}

How do I know if the exception thrown is the result of ToolStripButton click event?

The code above is helpless as Pressed property is false in the method, so the message box will not pop out.

Following is from my Program.cs file

[STAThread]
static void Main()
{
    Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new AppForm());
}

Experienced developers must have understood that I am trying to solve the well known issue with BindingNavigator. This control simply manages BindingSource component. In scenarios where strongly typed data sets are used, navigating through records throws exception crashing the application.

DotNet Developer
  • 2,973
  • 1
  • 15
  • 24
  • Your need to debug the `UserControl`, the source of the problem. For now, comment this `if (bindingNavigatorMovePreviousItem.Pressed)` and see what you get. Also, what is crashing? Visual Studio at debug mode or the released app? – dr.null Feb 16 '23 at 13:32
  • 2
    You are asking this question because what you *actually* discovered is that the code you added to try to handle the exception was a cure that's quite a bit worse than the disease. The SetUnhandledExceptionMode() call prevents you from debugging exceptions. The ThreadException event assignment causes loss of the default handler, causes a permanent leak of the UC and the form it is on and catches too many unrelated exceptions. Pressed doesn't work because the Click event is fired when the mouse is released. BindingNavigator was designed to work with the default exception handler. – Hans Passant Feb 16 '23 at 15:01
  • What do you mean with "default exception handler" that BindingNavigator was designed to work with. – DotNet Developer Feb 16 '23 at 15:23

2 Answers2

1

Just debug your application. If you have an idea where the exception is thrown, put a breakpoint there. When the exception is thrown, you can go back in the call stack. In the call stack, you should be able to see where the exception came from. From the code that you are providing, seems hard to help more.

Jazz.
  • 458
  • 4
  • 12
  • Focus on question title – DotNet Developer Feb 16 '23 at 07:30
  • Can you not add a breakpoint in the toolStripButton click event? Why cant you debug the behaviour? Using the proper tools for debugging, not MessageBoxes will get you closer to the solution. – Jazz. Feb 16 '23 at 07:35
  • 1
    The exception is not thrown from user code; basically there's no user code when you click on add/remove/navigation buttons. The code belongs to the BindingNavigator and you cannot set a breakpoint. – Reza Aghaei Feb 16 '23 at 21:03
1

When you click on BindingNavigator buttons, the exception is not thrown from your code, it's thrown in framework classes, so you cannot set a breakpoint. You need to avoid the exception, or if you want to handle it in Application.ThreadException, you need to check stack trace.

This post answers two questions:

  1. How to avoid exception when clicking on navigation buttons or add/remove/save buttons?
  2. How to know if ToolStripButton was involved in the actions which resulted in throwing an exception?

1 - How to avoid exception when clicking on navigation buttons or add/remove/save buttons?

Disable constraints in the form load. Then in the Save button click, enable them, handle the exceptions (if necessary) and disable them again:

private async void Form1_Load(object sender, EventArgs e)
{
    dataSet1.EnforceConstraints = false;
}
private void dataTable1BindingNavigatorSaveItem_Click(object sender, EventArgs e)
{
    try
    {
        dataSet1.EnforceConstraints = true;
    }
    catch (Exception ex)
    {
        //process errors if necessary
    }
    dataSet1.EnforceConstraints = false;
}

enter image description here

Any other option to avoid the exception?

Yes, as another option to avoid the error, you can just remove the default functionalities from buttons; for example select the binding navigator, and in properties find the AddNewItem and select (none) as its value. Then double click on the button to handle its click event and handle it like this:

private void bindingNavigatorAddNewItem_Click(object sender, EventArgs e)
{
    try
    {
        myBindingNavigator.BindingSource.AddNew();
    }
    catch (Exception)
    {
        //process the error, for example show a message
    }
}

2 - How to know if ToolStripButton was involved in the actions which resulted in throwing an exception?

This exception is not thrown from your code, but thrown in framework classes. Looking into the e.Exception.StackTrace of the exception that you have handled in Application.ThreadException, you see all the necessary details including class names and method names:

at System.Data.DataColumn.CheckNullable(DataRow row)
at System.Data.DataTable.RaiseRowChanging(DataRowChangeEventArgs args, DataRow eRow, DataRowAction eAction, Boolean fireEvent)
at System.Data.DataTable.SetNewRecordWorker(DataRow row, Int32 proposedRecord, DataRowAction action, Boolean isInMerge, Boolean suppressEnsurePropertyChanged, Int32 position, Boolean fireEvent, Exception& deferredException)
at System.Data.DataTable.InsertRow(DataRow row, Int64 proposedID, Int32 pos, Boolean fireEvent)
at System.Data.DataView.FinishAddNew(Boolean success)
at System.Data.DataRowView.EndEdit()
at System.Windows.Forms.CurrencyManager.EndCurrentEdit()
at System.Windows.Forms.BindingSource.EndEdit()
at System.Windows.Forms.BindingSource.AddNew()
at System.Windows.Forms.BindingNavigator.OnAddNew(Object sender, EventArgs e)
at System.Windows.Forms.ToolStripItem.RaiseEvent(Object key, EventArgs e)
at System.Windows.Forms.ToolStripButton.OnClick(EventArgs e)
at System.Windows.Forms.ToolStripItem.HandleClick(EventArgs e)
at System.Windows.Forms.ToolStripItem.HandleMouseUp(MouseEventArgs e)
at System.Windows.Forms.ToolStripItem.FireEventInteractive(EventArgs e, ToolStripItemEventType met)
at System.Windows.Forms.ToolStripItem.FireEvent(EventArgs e, ToolStripItemEventType met)
at System.Windows.Forms.ToolStrip.OnMouseUp(MouseEventArgs mea)
at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.ScrollableControl.WndProc(Message& m)
at System.Windows.Forms.ToolStrip.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

You can also get the stack frames yourself:

var frames = new StackTrace(e.Exception, true).GetFrames();

And then you can search between the frames, for example by checking:

Where(x => x.GetMethod().DeclaringType == typeof(ToolStripButton))
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398