views:

346

answers:

2

I am having an issue where an exception being thrown from within a properties set accessor is not being caught by my global exception handler.

I was having the issue in a larger application and after much gnashing of teeth troubleshooting the issue I tried, and succeeded in, replicating the issue in a simpler project.

Following is the code and behavior. Can someone explain this behavior please and how I should properly code to achieve the desired result, which is for the exception to be caught by the global event handler.

//Program.cs - Wire up global exception handling
static class Program
{
[STAThread]
static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
    Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

    Application.Run(new Form1());
}

static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
    MessageBox.Show("Exception occured : " + e.Exception.Message);
}

*

//In main form create instance of class containing bound property and setup databinding
//to numericUpDown control
private void Form1_Load(object sender, EventArgs e)
{
    _car = new Car();
    _car.NumberOfWheels = 4;
    numericUpDown1.DataBindings.Add(new Binding("Value", _car, "NumberOfWheels", true, DataSourceUpdateMode.OnPropertyChanged));
}

*

public int NumberOfWheels
{
    get { return _numberOfWheels; }
    set
    {
        if (value < 4)
            //Throw some exception
            throw new ArgumentNullException("Argument null exception trigger in Number Of Wheels property");

         _numberOfWheels = value;
    }

}

If I set a breakpoint at the 'throw new ArgumentNullException' line the program will certainly break at this point when I change the value of the numericUpDown control (which is bound to the NumberOfWheels property). However this is the only way to detect if the exception is thrown. No message is displayed via the UI that the exception being thrown, ie it is not caught by the global exception handler.

In constrast if I change the value of the property via a button click then the exception is thrown AND caught by my handler, with a message box being shown.

What am I missing?

A: 

The binding will "swallow" or "hide" the Exception. This is, presumably, to be more fault tolerant.

You need to listen for the BindingComplete event and check the BindingCompleteState on the BindingCompleteEventArgs BindingCompleteState property. If that value is BindingCompleteState.Exception, then the BindingCompleteEventArgs Exception property will contain the Exception that was thrown.

    public Form1()
    {
        InitializeComponent();

        Binding binding = new Binding("Value", _car, "NumWheels", true, DataSourceUpdateMode.OnPropertyChanged);
        numericUpDown1.DataBindings.Add(binding);
        binding.BindingComplete += new BindingCompleteEventHandler(binding_BindingComplete);

    }

    void binding_BindingComplete(object sender, BindingCompleteEventArgs e)
    {
        if (e.BindingCompleteState == BindingCompleteState.Exception)
        {
            throw e.Exception;
        }
    }
Mufaka
A: 

Thanks Mufaka

That was very helpful and allowed me expand my knowledge further on databinding. However the exception is still not being caught by the global exception handler. The code breaks at the last '}' of binding_BindingComplete and "ArgumentNullException was unhandled by user code" is displayed.

The only way I can get it to work as you describe is if I set

binding.FormattingEnabled = false;

but this appears to have it's own issues.

I think what I will do for now is take your approach but instead of throwing the exception again I will display a message box and restore original value.

private void binding_BindingComplete(object sender, BindingCompleteEventArgs e)
{
   if (e.BindingCompleteState == BindingCompleteState.Exception)
   {
       MessageBox.Show(e.Exception.Message);
       e.Binding.ReadValue(); //resets value to actual value of data source instead of leaving it as changed value of control
   }
}

I can probably use this handler for all databound controls

Regards

SBB

SleepyBoBos