WinForms and the big red 'X' of doom

WinForms and the big red 'X' of doom

  • Comments 9

You are making a WinForms app. All is going well, until one day your custom control disappears. In its place you just see a big red cross:


What gives?

Let us back up to remind ourselves how exceptions work:

  • If your code does something wrong (such as dereferencing a null object or passing invalid parameters to an API) an exception is thrown
  • If your code catches the exception, it can handle the error and continue along its merry way
  • If the exception is not caught, the program terminates
  • If the debugger is attached when an unhandled exception occurs, it pops up a window showing the exception message and callstack, so you can see exactly what went wrong

WinForms extends this system in one important way:

  • It wraps calls to your Control.OnPaint method in a try/catch block
  • If OnPaint throws an exception, this is now caught by WinForms rather than left unhandled
  • WinForms keeps track of whether OnPaint has ever thrown an exception
  • If so, it skips any later calls to OnPaint, and instead just draws a red cross

I don't know why WinForms decided to do this, but it's kinda annoying because if anything goes wrong in your OnPaint code, the debugger never gets to see the exception so has no chance to show you the exception message!

To debug such problems, open up the Debug \ Exceptions menu in Visual Studio and check the Thrown box next to Common Language Runtime Exceptions. Now the debugger will break whenever any exception is thrown, regardless of whether or not it is caught further up the stack, so you can see the original message and callstack in spite of WinForms trying to catch this error.

  • I'm not as interested in the content of the post as much as to *why* you posted this? What cool new Xna features that Shawn is working on requires a Winform Custom Control?

  • I think the reason probably is because OnPaint() is being called from within the Windows message loop (or more specifically, the WndProc callback) and WinForms can't just let the exception travel upwards into the code that called WndProc.

    I wonder why they don't just remember the exception and destroy the window right after the message has been processed - and then rethrow the exception out of Application.Run() / Form.ShowDialog() or whatever is running the message loop at that time.

  • Also the red cross is a bit less annoying than it jumping into the debugger if you're using design time tools.

  • I mostly posted this so I have something to link to the next time someone in the forums runs into this issue.  Any time I find myself answering the same question for the third or fourth time, I try to turn it into a blog post for future reuse!

  • @Cygon: Just about *everything* done in a Windows Forms app is done via a call from the Windows message loop; and everywhere else the exception is allowed to bubble up (see any button's Click event for one example).  The AppDomain's unhandled exception handler or, failing that, the CLR's handler is perfectly capable of catching it and displaying it, even if the exception blows through WndProc.

  • @Timothy: True, there is an exception handling mechanism in WinForms (except that it's Application.ThreadException, not AppDomain.UnhandledException). So, any idea why they didn't do it here? To avoid a Hall-of-Mirrors like effect for controls not drawing themselves perhaps?

  • While that solution is nice, i find it a nuissance to see every handled exception. For example some functions like file io may throw exceptions, that can be handled very well. If i'd see them all in a large project, it sometimes may be too much. It's maybe because i avoid exceptions only in the main loop(OnPaint) and handle them in loading, network, database code.

    So i put a try catch block around the contents of the OnPaint method and call Debugger.Break() in the catch area. That lets me see every unhandled exception in the main loop. AFAIK try has only very little overhead, if no exception occurs, so there isn't a real performance penalty with this method either.


  • @Mad Martin:

    Go to Tools -> Options, Debugging, and check "Enable Just My Code (Managed Only)". Then, any function that has handled exceptions you can stick a [System.Diagnostics.DebuggerNonUserCode]. Any handled exception in libraries will be ignored as well.

    I find this an incredibly useful feature to know, and I'm glad Shawn's getting it out there.

    For info on Just My Code you can check:

  • Thank you, thank you, thank you!  This was exactly the concise explanation for which I was searching.

Page 1 of 1 (9 items)
Leave a Comment
  • Please add 5 and 1 and type the answer here:
  • Post