Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WinForms Validating event prevents Escape key closing the form

I have a simple Form with a single TextBox, plus OK and Cancel buttons. The Form's AcceptButton and CancelButton are set correctly, and the OK and Cancel buttons have their DialogResult set to 'OK' and 'Cancel'.

I want to add validation to the TextBox which will prevent the user from OK-ing the form when validation fails, but which will also allow them to cancel as usual.

The CausesValidation property is True by default on all the controls, but I have changed this to False on the Cancel Button.

Sure enough, clicking OK or pressing the Enter key will run the Validating event I wired up to the TextBox. Pressing the Cancel button bypasses Validating, which is perfect.

However, pressing Escape to cancel the form does not perform the same as pressing the Cancel button - it raises the Validating event and prevents the user from exiting.

Is there any way of making the Escape key perform as intended, i.e. not raise the Validating event, just as if the Cancel button had been pressed?

A complete worked solution is:

Create a new Windows Forms app. Add a second Form to the project.

Paste this code into Form1's constructor, after InitializeComponent():

MessageBox.Show((new Form2()).ShowDialog().ToString());

This shows the DialogResult passed back from our second form.

Paste this code into Form2's constructor, after InitializeComponent():

TextBox txtName = new TextBox();

txtName.Validating +=
    new CancelEventHandler((sender, e) =>
    {
        if (txtName.Text.Length == 3)
        {
            MessageBox.Show("Validation failed.");
            e.Cancel = true;
        }
    });

Button btnOk = new Button
{
    Text = "OK",
    DialogResult = DialogResult.OK
};
Button btnCancel = new Button
{
    Text = "Cancel",
    CausesValidation = false,
    DialogResult = DialogResult.Cancel
};
FlowLayoutPanel panel = new FlowLayoutPanel();
panel.Controls.AddRange(new Control[] 
{
    txtName, btnOk, btnCancel 
});

this.AcceptButton = btnOk;
this.CancelButton = btnCancel;

this.Controls.Add(panel);

In this simplified example the textbox will not let you proceed if there are 3 characters input. You can press the Cancel button or close the form directly even if there are 3 characters present; however pressing the Escape key will not do the same - it fires the Validating event whereas it should be doing the same as pressing Cancel.

like image 556
Dave R. Avatar asked Jan 25 '11 23:01

Dave R.


2 Answers

Yes, this an awkward quirk of the ValidateChildren method. It doesn't know that canceling was intended. Paste this code to fix the problem:

    protected override void OnFormClosing(FormClosingEventArgs e) {
        base.OnFormClosing(e);
        e.Cancel = false;
    }

To avoid having a Validate event handler running that causes side-effects, like a message box, add this statement to the top of the method:

    private void txtName_Validating(object sender, CancelEventArgs e)
    {
        if (this.DialogResult != DialogResult.None) return;
        // etc..
    }

Paste this code into your form to get the DialogResult set before it tries to validate the form:

    protected override bool ProcessDialogKey(Keys keyData) {
        if (keyData == Keys.Escape) this.DialogResult = DialogResult.Cancel;
        return base.ProcessDialogKey(keyData);
    }
like image 91
Hans Passant Avatar answered Oct 26 '22 07:10

Hans Passant


I just saw this problem as I was hunting a solution for the same and the override of ProcessdialogKey is the MS-approved solution until they fix the bug (Escape should do the same as clicking Cancel). A discussion of this bug is also found here (just working with Visual Basic instead of C#. Bug is over 5 years old and apparently still not fixed): Bug or Feature? CancelButton vs Escape Key I am trying to work out the C++ solution.

Edit to add: The solution from the link in C#:

protected override bool ProcessDialogKey(Keys keyData)
{
    if (keyData == Keys.Escape)
    {
        this.AutoValidate = AutoValidate.Disable;
        cancelButton.PerformClick();
        this.AutoValidate = AutoValidate.Inherit;
        return true;
    }
    return base.ProcessDialogKey(keyData);
}
like image 29
galmok Avatar answered Oct 26 '22 06:10

galmok