views:

382

answers:

5

Hi all, I apologize if this is a simple question (my Google-Fu may be bad today).

Imagine this WinForms application, that has this type of design: Main application -> shows one dialog -> that 1st dialog can show another dialog. Both of the dialogs have OK/Cancel buttons (data entry).

I'm trying to figure out some type of global exception handling, along the lines of Application.ThreadException. What I mean is:

Each of the dialogs will have a few event handlers. The 2nd dialog may have:

private void ComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
    try
    {      
        AllSelectedIndexChangedCodeInThisFunction();
    }
    catch(Exception ex)
    {
        btnOK.enabled = false;  // Bad things, let's not let them save
        // log stuff, and other good things
    }
}

Really, all the event handlers in this dialog should be handled in this way. It's an exceptional-case, so I just want to log all the pertinent information, show a message, and disable the okay button for that dialog.

But, I want to avoid a try/catch in each event handler (if I could). A draw-back of all these try/catch's is this:

private void someFunction()
{
    // If an exception occurs in SelectedIndexChanged,
    // it doesn't propagate to this function
    combobox.selectedIndex = 3; 
}

I don't believe that Application.ThreadException is a solution, because I don't want the exception to fall all the way-back to the 1st dialog and then the main app. I don't want to close the app down, I just want to log it, display a message, and let them cancel out of the dialog. They can decide what to do from there (maybe go somewhere else in the app).

Basically, a "global handler" in between the 1st dialog and the 2nd (and then, I suppose, another "global handler" in between the main app and the 1st dialog).

Thanks.

+1  A: 

You may be able to use the AppDomain.CurrentDomain.UnhandledException handler to intercept the errors on the main UI thread and handle them per-dialog. From MSDN:

In applications that use Windows Forms, unhandled exceptions in the main application thread cause the Application.ThreadException event to be raised. If this event is handled, the default behavior is that the unhandled exception does not terminate the application, although the application is left in an unknown state. In that case, the UnhandledException event is not raised. This behavior can be changed by using the application configuration file, or by using the Application.SetUnhandledExceptionMode method to change the mode to UnhandledExceptionMode.ThrowException before the ThreadException event handler is hooked up. This applies only to the main application thread. The UnhandledException event is raised for unhandled exceptions thrown in other threads.

LBushkin
+1  A: 

Sounds like you want aspects. PostSharp could help you out.

Austin Salonen
+1  A: 

You may like to rethink the design of your application slightly if you're doing stuff in combobox event handlers that might throw exceptions.

An alternative would be to initialise the dialog with all the information it needs before showing it to the user. The user then makes selections, and presses OK, and then the parent dialog could process the information in the dialog.

The exception handling could then be done in the parent dialog.

Of course this wouldn't be appropriate if you need to dynamically update the data in the dialog based on user actions...

e.g.

MyDialog myDialog = new MyDialog();
myDialog.Init(//data for the user to choose/manipulate);
if(myDialog.ShowDialog() == DialogResult.OK)
{
try{
ProcessDialogData(myDialog.SomeDataObject);
}
catch(/*...*/}
}

HTH

serial
As I mentioned elsewhere, this is an enterprise app so there is program auditing (user clicked this button, etc.) and logging in a lot of our events. Those are the types of things I think can fail. You're right, SelectedIndexChanged code (outside of what I mentioned) rarely fails. Maybe I chose a bad event in my question.
JustLooking
+1  A: 

Global exception handling in WinForms application is done using two handlers: Application.ThreadException and AppDomain.CurrentDomain.UnhandledException. ThreadException catches unhandled exceptions in the main application thread, while CurrentDomain.UnhandledException catches unhandled exceptions in all other threads. Global exception handling may be used for the following purposes: showing user-friendly error message, logging the stack trace and other useful information, cleanup, sending error report to developer. After unhandled exception is catched, application should be terminated. You may want to restart it, but it is impossible to correct an error and continue, at least, in non-trivial applications.

Global exception handling is not replacement for local exception handling, which still should be used. Local exception handlers should never use catch Exception, because this effectively hides programming bugs. It is necessary to catch only expected exceptions in every case. Any unexpected exception should crash the program.

Alex Farber
+2  A: 

Yes, the default handling of Application.ThreadException was a mistake. Unfortunately, it was a necessary mistake, needed to not immediately discourage and despair hundreds of thousands of programmers writing their first Windows Forms application.

The fix you are contemplating is not a fix, it has a lot of potential to make it worse. While a user clicking the Continue button on the exception dialog is a questionable outcome, swallowing exceptions in a global exception handler is much worse.

Yes, do write a replacement handler for ThreadException. Have it display the value of e.Exception.ToString() in a message box so the user has some idea what blew up. Then fire off an email or append to an error log so you know what went wrong. Then call Environment.FailFast() so no more damage can be done.

Do the same for AppDomain.CurrentDomain.UnhandledException. It won't get much of a workout.

Use the feedback to improve your code. You'll find out where validation is required. You can help the customer's IT staff diagnose trouble with their LAN and equipment. And you'll find the very few cases where your own try/catch blocks might be able to recover from the exception.

Hans Passant
Excellent response!
One quick follow-up: Let's say you are logging (which is happening in pretty much all events), and it fails. What do you do? Should the logger throw an exception (and thus close down the whole app when caught in the global handler)? Does your logger throw an exception (or just return an error code)? How do we determine that the logging has failed? We can't log it. Should the user get a message box? Long-winded, yes, but it's all the logging and "program auditing" (which goes to a database) that are my concern. That's why I've been in the habit of try/catching all events.
JustLooking
@JustLooking: what do you do when you trip over the power cord and unplug the machine? You ask somebody to re-route the cable so it doesn't happen again. Yes, message box.
Hans Passant
@nobugz: Messagebox at the 2nd dialog? (without closing things down)? Or the MessageBox presented at the global exception handler (and the whole app comes down)? (Pardon me if I've side-tracked, I'm curious as to your take)
JustLooking