4

I'm in the middle of adding new functionality to my winforms controls, and part of that requires that a variable that was once always used to now be optional (if it's null, get the data from a second source). I made some changes and ran my form, only to find out nothing was happening, even functionality that previously worked. Confused I stepped through the code and found out that my Winforms user control was throwing a NullReferenceException when it encountered my variable, but in the UI no errors were being thrown.

My setup is I have a UserControl with a combo box. When the user changes that combo box it loads a secondary UserControl in a panel the first control has. The second control is what is throwing the exception.

Here are the code paths:

    private void cmbActionType_SelectedIndexChanged(object sender, EventArgs e)
    {
        if (_loading)
            return;

        // ActionType was changed, update the action.ActionType value
        if (cmbActionType.SelectedItem != null)
        {
            if (cmbActionType.SelectedItem.ToString() == SETVALUE_OPTION)
                _action.ActionType = ActionTypes.SetValue;
            else if (cmbActionType.SelectedItem.ToString() == CHECKVALUE_OPTION)
                _action.ActionType = ActionTypes.CheckValue;
            else
                _action.ActionType = ActionTypes.CustomAction;
        }

        RefreshActionPanel();
        _editor.DataModified();
    } 

    private void RefreshActionPanel()
    {
        // Control defaults
        AnchorStyles styles = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top;
        UserControl subControl = null;

        // Clear the currently active control
        pnlActionDetails.Controls.Clear();

        // Determine what type of control to load in the panel
        if (cmbActionType.SelectedItem != null && cmbCaseType.SelectedItem != null)
        {
            // SetValue or CheckValue actions
            if (cmbActionType.SelectedItem.ToString() == CHECKVALUE_OPTION || cmbActionType.SelectedItem.ToString() == SETVALUE_OPTION)
            {
                if (_caseTypeMap.ContainsKey(cmbCaseType.SelectedItem.ToString()))
                    subControl = new SetCheckActionControl(_action, _editor, _caseTypeMap[cmbCaseType.SelectedItem.ToString()]);
            }

            // CustomAction action type
            else
            {
                // Check if the requested case is a type or defined in a script
                if (_caseTypeMap.ContainsKey(cmbCaseType.SelectedItem.ToString()))
                {
                    subControl = new CustomActionControl(_action, _editor, _caseTypeMap[cmbCaseType.SelectedItem.ToString()]);
                }

                else if (_editor.ScriptDefinitions.Any(x => x.CaseName == cmbCaseType.SelectedItem.ToString()))
                {
                    var definitions = _editor.ScriptDefinitions.Where(x => x.CaseName == cmbCaseType.SelectedItem.ToString()).ToList();
                    subControl = new CustomActionControl(_action, _editor, definitions);
                }
            }
        }

        if (subControl != null)
        {
            subControl.Anchor = styles;
            subControl.Height = pnlActionDetails.Height;
            subControl.Width = pnlActionDetails.Width;
            pnlActionDetails.Controls.Add(subControl);
        }
    }

    public CustomActionControl(TestAction action, fmEditor editor, IList<TcScriptDefinition> scriptDefinitions) : base(action, editor)
    {
        _loading = true;
        InitializeComponent();

        _scriptDefinitions = scriptDefinitions;

        PopulateActionList();
        SetupDataGrid();

        _loading = false;
    }

    private void SetupDataGrid()
    {
        // Clear the current contents of the datagrid
        grdParameters.Rows.Clear();

        if (cmbAction.SelectedItem == null)
            return;

        // Retrieve the action code from the drop down
        string actionCode = cmbAction.SelectedValue.ToString();

        // Check if any paramters are available for this action
        if (!_availableActionParameters.ContainsKey(actionCode))
            return;

        // Add a new row for each parameter available for this action
        foreach (string param in _availableActionParameters[actionCode])
        {
            string display = param;

            // Determine if the parameter has a display string
            if (_formInstance.CodeDisplayMap.ContainsCode(param))
                display = _formInstance.CodeDisplayMap.GetDisplayStringFromCode(param);

            // Create the array for the row, with an empty string as the current value
            string[] row = { display, string.Empty };

            // Check if the current action uses this action code.  
            //   If so, retrieve the value for this parameter and use it in the row
            //   Note: Case-INsensitive comparison must be performed here
            if (_action.Attributes["action"].Equals(actionCode, StringComparison.CurrentCultureIgnoreCase))
                if (_action.Attributes.ContainsKey(param))
                    row[1] = _action.Attributes[param];

            grdParameters.Rows.Add(row);
        }
    }

The NullReferenceException is coming from the SetupDataGrid() method where _formInstance is being called. However, usually when an application encounters an unhandled exception the JIT system throws an error message saying such (and as you can see, there's no try/catch statements used unless I am blind).

Why does my winforms application show no signs of an exception occurring. I'd rather an unhandled exception message occur rather than nothing happening, as that makes it harder for users to know something critical went wrong (as opposed to it not responding to their commands)


Edit: To clarify since there seems to be some confusion, I do NOT care about breaking on this exception in visual studio when debugging. The fact of the matter is that the application should not be hiding unhandled exceptions, and my application should crash (or rather show the JIT message that an unhandled exception occurred), even when not in debug mode outside of visual studio.

This is not a debug time question but a production run time question. If this code throws an OutOfMemoryException for instance, I need it to not be silently ignored. Right now it is being ignored.

KallDrexx
  • 27,229
  • 33
  • 143
  • 254
  • 1
    Is this code running in a separate thread to the UI? – Connell Jul 26 '11 at 15:17
  • @Connell: Right, +1. Or there's some `try..catch` wrapping the entire application process much higher up. – Yuck Jul 26 '11 at 15:18
  • This code is all running in the main form's UI. I'm not backgrounding the user controls, and no I'm not doing any try/catch in the entire application process, or using any global handlers. – KallDrexx Jul 26 '11 at 15:19
  • Why is `_formInstance` null in the first place? What is `_formInstance`? (obviously an instance of a `Form`, but which form?) – Connell Jul 26 '11 at 15:22
  • It's probably not the best named, it's not referring to a winform but a "testing form" custom type (bad naming conventions for a custom class that this app started with) – KallDrexx Jul 26 '11 at 15:24
  • Also, if it matters the form these controls are used in are visible in a non-blocking windows form, loaded using the `Form.Show()` method. So I guess you might be right about it being in a separate thread than the core application, but it's still in a UI thread. – KallDrexx Jul 26 '11 at 15:26
  • Would your program still function if you used the `ShowDialog()` method? If so, do so just to test it and see if you see your error message – Connell Jul 26 '11 at 15:32
  • No errors are thrown even when using `ShowDialog()` – KallDrexx Jul 26 '11 at 15:34

2 Answers2

3

Go to Debug->Exceptions... (Ctrl-D, E if you are using default shortcuts) from your menu bar in Visual Studio and check the box for Thrown under Common Language Runtime Exceptions. This will cause your program to break on exceptions even if they are in a try/catch block. You can then do a step forward and see where the code is jumping to for the next instruction. That should bring you to the catch block that is hiding your exception.

It may be jumping in to .NET code for the catch, you can go to Debug->Options and Settings.. and turn on Enable .NET framework Source Stepping to see what it is jumping in to.


Here is a example of catching the unhandeled execptions in code

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
        Application.Run(new Form1());
        MessageBox.Show("0");
    }

    static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
    {
        MessageBox.Show("1");

    }
}
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {

        throw new ArgumentNullException();
    }
}

Click the button and 1 appears, the program should still run afterwards.

However as I said in my last comment, check that user-unhandeled is checked (maybe uncheck and recheck) in Debug->Exceptions first it may solve your initial issue..

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • I tried setting both of these, but once I break on the `NullReferenceException` and click "Step Into", the UI comes back fully responsive, no other code is getting stepped to after the fact. – KallDrexx Jul 26 '11 at 16:22
  • When it breaks on the exception pull up the threads window, is it on the main thread or is it running on a worker thread? – Scott Chamberlain Jul 26 '11 at 16:36
  • How do I bring up the threads window? I don't see it in the View->Other Windows menu, and by default all I have below are the call stack and immediate window, windows – KallDrexx Jul 26 '11 at 16:45
  • Then you need to follow @Stephen Cleary's advice. I will update my post with a example. – Scott Chamberlain Jul 26 '11 at 16:50
  • Check that User-Unhanded is checked in Debug->Execptions for Common language run time. does that solve the issue? – Scott Chamberlain Jul 26 '11 at 16:57
  • This isn't just a debug time issue, this is also an issue when running the application outside of visual studio – KallDrexx Jul 26 '11 at 17:01
2

They can be caught if you have a try/catch in your Main, or if you have a ThreadException handler. Also check out SetUnhandledExceptionMode.

Note that if they're caught in Main, your program will just exit silently. ThreadException can "catch" and ignore them.

EDIT: Since the debugger doesn't break when you uncheck the box for Thrown under Common Language Runtime Exceptions, it's being caught by the code for Windows Forms. This is not ideal, but a lot of the BCL does this. You don't need to worry about it catching OutOfMemoryException or anything like that; the BCL should only be catching exceptions that it expects (vexing exceptions).

To be clear, the exception is not "unhandled". It is handled by the BCL code.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • All 3 modes in `SetUnhandledExceptionMode` don't cause the application to show the unhandled exceptions, and I don't want to explicitly catch all exceptions for this (I don't want to explicitly deal with OOM exceptions for instance). I am more curious on why my app isn't showing unhandled exceptions rather than how to catch them. – KallDrexx Jul 26 '11 at 15:30
  • Right. I meant to *check* if your application has a `ThreadException` handler, and what the unhandled exception mode *is*. – Stephen Cleary Jul 26 '11 at 15:39
  • Oh sorry. I can't find any thread exception handlers anywhere (and searching the project for `ThreadException` brings nothing up) – KallDrexx Jul 26 '11 at 16:23
  • Well, the only other thing that comes to mind is to *uncheck* the box for `Thrown` under `Common Language Runtime Exceptions` and see if it goes away. – Stephen Cleary Jul 26 '11 at 16:26
  • It is unchecked, and it doesn't break. However, I want the exception to show in the main application, I don't want the application to hide unhandled exceptions – KallDrexx Jul 26 '11 at 16:52
  • Ohh I see. Stupid question, what's BCL? – KallDrexx Jul 26 '11 at 17:48
  • The BCL is the "base class library", though in my answer above I'm using "BCL" to also refer to the Windows Forms library (technically incorrect, but I didn't want the answer to be confusing). – Stephen Cleary Jul 26 '11 at 17:51
  • Ok excellent. This perfectly describes why I am seeing this. :) – KallDrexx Jul 26 '11 at 17:54