views:

31

answers:

1

I'm writing a Windows application that basically runs in the background with a notification icon to interact with it. The notification icon can do basic things like exit the application or show information about it. It can also launch a modal configuration dialog.

The code that creates the dialog is pretty straightforward:

using(var frmSettings = new SettingsForm(configuration))
{
    frmSettings.ConfigurationChanged += ConfigurationChangedHandler;
    frmSettings.UnhandledException += UnhandledExceptionHandler;

    frmSettings.ShowDialog();
}

The SettingsForm class basically has three GroupBox controls, with a Label and TextBox control in each, and 4 Button controls at the bottom: "Advanced...", "Restore Defaults", "Cancel", and "Apply". Each TextBox has a Validating event handler wired up through the designer. Each button has a Click handler wired up through the designer. Each of them does pretty obvious things: opens another modal dialog with more advanced settings, restores the textboxes to their default values, closes the dialog, or saves the changes, fires the ConfigurationChanged event, and then closes the dialog (but only if all fields are valid!).

When there is a form entry error I cancel the corresponding Validating event by setting ((CancelEventArgs)e).Cancel = true. However, the default behavior of both forms was to prevent the user from changing focus when validation failed. I found this pretty annoying and eventually found the option in the designer to still automatically validate when the user leaves the field, but to allow them to leave even if validation fails: AutoValidate = EnableAllowFocusChange.[1]

My "Apply" button Click handler looks basically like this:

private void btnApply_Click(object sender, EventArgs e)
{
    try
    {
        if(this.ValidateChildren())
        {
            this.Configuration.Field1 = this.txtField1.Text;
            this.Configuration.Field2 = this.txtField2.Text;
            this.Configuration.Field3 = this.txtField3.Text;

            if(this.Configuration.Changed)
            {
                this.Configuration.Save();

                this.OnConfigurationChanged(new ConfigurationChangedEventArgs(
                        this.Configuration));
            }

            this.Close();
        }
    }
    catch(Exception ex)
    {
        this.OnUnhandledException(new UnhandledExceptionEventArgs(
                "Failed To Apply Configuration Settings",
                ex));
    }
}

I'm currently testing out the code by breaking on the first line and stepping through the method line by line. Essentially, ValidateChildren is returning false as expected and the entire if block, including the this.Close() are skipped. Yet, if I step all the way to the bottom of the method and then step out of it I end up back on the frmSettingsForm.ShowDialog() line and the form is magically closed.

The "Apply" button is set as the form's AcceptButton. I wonder if it's implicitly attached a handler to the button's Click event to automatically close the form when the button is pressed. That doesn't sound like it logically should be assumed, especially considering there doesn't seem to be a way to cancel the Click event, but it's the only explanation that I can come up with. To test that theory, I have tried unsetting the AcceptButton in the designer, but my form still closes when the data is invalid.

What is closing my form and how do I stop it?

[1]: If anybody else has trouble finding it, it's a form property, not a property of each individual control (as I expected it would be).

+4  A: 

Do you have the DialogResult of the Button set? If so, when you click the Button, the DialogResult of the Form will be set to that value and the modal Form will close. To prevent this, when validation fails in your Click handler, set the Form's DialogResult to DialogResult.None.

Mike Dour
Thank you, that appears to fix the problem! I didn't explicitly set `DialogResult` to anything before so it must be implicitly set (or default to something other than `None`). Seems kind of shifty, but I guess it must work out for the majority of forms. Anyway, I just added an else block and set it to `None` as you suggested (and in the `catch` block as well) and it seems fixed!
bamccaig
Maybe it got set automatically when you set the `Button` to the `Form.AcceptButton`. I tried it in VS2008 and it didn't do that, but maybe you are using a different version or you have some add in that is doing it for you.
Mike Dour
I am using VS2010 and .NET 4. Perhaps things have changed since VS2008 / 3.5?
bamccaig
Actually it seems that the `Button` class has a `DialogResult` property too, which is copied into the form when you click the button. It seems the default value is supposed to be `None`, but somehow the `DialogResult` for my `"Apply"` button was set to `Cancel`. I guess that explains it. - http://msdn.microsoft.com/en-us/library/system.windows.forms.form.dialogresult.aspx
bamccaig
I know what probably happened then. You probably created a cancel button and assigned it to the `Form.CancelButton` property. This assigns the `DialogResult` of the `Button` to `DialogResult.Cancel`. Then you made a copy of the cancel button on the `Form` (which also copied the value of its `DialogResult` property) and modified the Text of the new button and made it the `Form.AcceptButton`, but you never reset its `DialogResult` value.
Mike Dour