WinForms and the big red ‘X’ of doom

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.

Comments (14)

  1. Michael Cummings says:

    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?

  2. Cygon says:

    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.

  3. Christopher Hawkins says:

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

  4. ShawnHargreaves says:

    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!

  5. Timothy Fries says:

    @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.

  6. Cygon says:

    @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?

  7. Mad Martin says:

    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.


  8. Erzengel says:

    @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:…/understanding-just-my-code.aspx…/h5e30exc.aspx

  9. Mark Meuer says:

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

  10. Sebastian Ekman says:

    YES! Thank you! now i can see where the error in my code was and it helped med alot. Thx!

  11. Martin says:


    I'm having a problem with a red cross in an MS chart component.

    When i start up the window with the chart component an invalid operation exception is thrown. I can get visual studio to break (ticking "Break when thrown" for invalid operation exception) but visual studio only breaks at the entry point of the program (Application.Run(new MainForm());).

    Any ideas how i can get visual studio to break where the exception actually is thrown?

    Best regards


  12. Marko says:

    I got this error in my application where I read data from a file and show them as graphs. I got this error when one Y-value was way off. The Y-value had the value 3.15486e+29 in the file. If I limited all values then it worked. Strange :-/

  13. Marko says:

    This will make the chart crash:

    namespace WindowsFormsApplication1
    public partial class Form1 : Form
    public Form1()

    chart1.Series[0].ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Line;
    chart1.Series[0].Points.AddY(3.15486e+29); // This big value makes the chart crash

  14. Colby-Tait Bricklayer says:

    My solution was to code review the hell out of it and have the guy do 3-4 iterations until the code stopped generating the exception. Good-old defensive programming.

Skip to main content