-1

I have a MDI parent / children application.
In the Program.cs file I have a global exception handler for both ThreadException and UnhandledException.

Those are working fine.

When I get an unhandled exception at the global level, inside the UnhandledException handler I call Environment.Exit(1) to close the application since I don't know the current state of the application.

In the child forms I "normally" add the following to event handlers.

try
{
    // Some Code
}
catch (Exception ex)
{
    HandleException(ex);
    MessageBox.Show("Some message");
    this.Close();
}

I would like to know if there is a way of adding a global exception handler for all my child forms (I do have a base form they inherit from) that can catch exceptions and close the child form without closing the entire application.
This way if a developer "forgot" to add the try catch block on an event, it does not bomb the entire application.

Thomas Weller
  • 55,411
  • 20
  • 125
  • 222
goroth
  • 2,510
  • 5
  • 35
  • 66
  • I agree that a crash dump would be handy. I do have a global exception logger that saves the stack traces, some performance settings, etc... that are saved in a sql database for reviewing but I don't want to kill the application just because someone forgot to add a try catch for something as simple as putting a string in a numeric text box. – goroth Aug 29 '17 at 20:32

2 Answers2

0

As I already said in the comments, I don't really like your approach, especially the case that you're catching the most general Exception.

The message will only partially help you fix the problem. A crash can help you completely fix the problem, including call stack, objects on the heap, CPU registers etc. Just learn How to take a crash dump and analyze it. By closing an MDI form, you ignore the fact that the data of your application may be in an invalid state

But ok, that's coding style and maybe that should not be discussed here.

What you could do is called Aspect Oriented Programming (APO) [Wikipedia]. Aspects are something that apply to several parts of your code and you don't want to implement it explicitly everywhere.

You implement an aspect as code and then define in a config file where you want that code to be applied, e.g. by a naming convention.

The magic happens at compile time. After your normal code was compiled, an intermediate code weaver (IL-weaver) modifies the IL code and inserts the aspect everywhere.

You'll need a library like PostSharp (commercial) or AfterThought (free). PostSharp even has examples for exception handling, which can easily be adopted to display a message box.

It needs some mind-bending when you first try it, so be sure to follow some tutorials before you apply that conceopt to your code.

Thomas Weller
  • 55,411
  • 20
  • 125
  • 222
  • I do agree with you that closing the MDI child and leaving the application running when an unhandled exception has occurred is bad practice but in this application all MDI forms are independent from each other and there are not global / static methods / properties / variables inside the application. So in this application it is safe to kill a form without killing the entire application. BUT I do like the links / information you have sent and will read each of them for future use. – goroth Aug 30 '17 at 02:16
0

I agree that this is a bad idea in general, but if your global exception handler has a "sender" object, you could try using sender.GetType() to get the type of the object. You can see if it inherits from your base form by saying if (sender.GetType().IsSubclassOf(typeof (BaseClass))) { // then do something }.

If the sender is a child control of the child form (rather than the form itself), then you may need to walk up a few levels of ancestors using a helper method to find the parent form (eg: sender might be a textbox, sender.Parent might be a panel, sender.Parent.Parent might be the form). You can recursively check the parents until either the Parent is null, or the parent derives from your base class.

You can also loop through the MdiParent form's MdiChildren to see if any of them are the form that caused the exception. Then you wouldn't need to inherit from the base class.

Also, in the case of a ThreadException, if you have some anonymous method in a fire-and-forget type thread, this would likely not work...

I only use global exception handling in one project, and I seem to remember that it doesn't work normally in debug mode because you get the Visual Studio exception helper instead, so I don't have a quick way of testing this - just consider it a general "pseudocode" recommendation...

Dave Smash
  • 2,941
  • 1
  • 18
  • 38
  • Of course, if you launch a thread and you aren't expecting it to interact with your form again, then you could in theory just swallow the exception and the form would continue running (again - creates a huge potential for invalid states, data corruption, etc, but you could do it). If your form is waiting for the thread to finish executing, then you would have to catch the error at that level (by having a meaningful timeout and realizing that the expected action never completed.) – Dave Smash Aug 29 '17 at 21:17
  • You can programatically access the stack trace using System.Diagnostics. `string fileName = new StackTrace().GetFrame(1).GetFileName();` would give you the file name for frame 1 - you could walk up the frames, and there are other methods like GetFileName() for getting the method name, type, etc. – Dave Smash Aug 29 '17 at 21:46
  • With your help I got very close. I ended up using a foreach loop on "new StackTrace(exception, true).GetFrames()" and then using "bool cancelExit = stackFrame.GetMethod().ReflectedType.IsSubclassOf(typeof(Project.BaseForm));" BUT then I found out that since I don't / can't get the instance of the form that crashed, I can't close / dispose that broken form... If only I can figure out how to get the handle ID of the form, I could send it a Windows Message to close that form. Still digging. – goroth Aug 30 '17 at 02:08
  • If you have the name or type of the child form, you can loop through the MDI children of the MDI Container to look for it, which might be tougher if you have 6 of the same form open. The problem with your approach is that if your child form crashes on a SQL query every time you click "Save", the user will believe that the data saved and you will have inconsistent data. The developers will have trouble debugging the problem because you are swallowing the exception. I think a better practice would be to use global exception handling to log the error and then still allow the program to crash. – Dave Smash Aug 30 '17 at 13:51
  • I would never recommend eating the exception. All exceptions should be logged and then display some notification to the caller that something went wrong. I just want to kill the form not the app. Anyway, the information you have given me is very helpful but I have come to the conclusion that Microsoft does not offer an easy way to catch unhandled exceptions at the form level without try catch blocks on all event handlers. I also know that adding try catch blocks with exception handlers on all events is best practice but sometimes developers forget... – goroth Aug 30 '17 at 14:17