-->

How to prevent controls from capturing KeyDown eve

2019-02-26 03:02发布

问题:

I have a form with CancelButton and AcceptButton (named btnCancel and btnOK). And I have some ComboBoxes as input fields.

ComboBoxes prevent my AcceptButton and CancelButton to receive Escape and Enter keys, so I added this code to KeyDown event for all fields:

if (e.KeyData == Keys.Escape)
{
    ComboBox field = (ComboBox)sender;
    if ((field.DropDownStyle == ComboBoxStyle.Simple) || (!field.DroppedDown))
    {
        e.SuppressKeyPress = true;
        btnCancel.PerformClick();
    }
}
else if (e.KeyData == Keys.Enter)
{
    ComboBox field = (ComboBox)sender;
    if ((field.DropDownStyle == ComboBoxStyle.Simple) || (!field.DroppedDown))
    {
        e.SuppressKeyPress = true;
        btnOK.PerformClick();
    }
}

This is the code in Clicked event of OK button:

if (!changesAreSaved)
{
    SaveChangesToNode();
}

List<int> invalidIndices = ValidateAndRefineNodes(true);

if (invalidIndices.Count == 0)
{
    this.DialogResult = DialogResult.OK;
    this.Close();
}
else
{
    MessageBox.Show(this, "Enter correct values for all fields before you press OK.", "Cannot Save Information",
        MessageBoxButtons.OK, MessageBoxIcon.Error);
}

Everything is OK but when a ComboBox has Focus and I press Enter key on my keyboard, btnOK_Clicked calls Fields_KeyDown again only when it shows its MessageBox (on else part of if). Exactly right after MessageBox.Show(...) is being called, KeyDown event is being called for second time without any reason.

This is Call Stack for first call:

And this is for second:

Second call should not occur at all. In second Call Stack, first btnOK_Click (third line) again calls Fields_KeyDown (second line) from MessageBox.Show(...). How is this possible? I'm confused...

Call Stack for second call with External Code visible:

回答1:

You cannot correctly process Escape and Enter key in KeyDown event because they are handled during the keyboard preprocessing phase - Control.IsInputKey and Control.ProcessDialogKey. Normally controls do that for you, but looks like there is a bug in ComboBox implementation when DropDownStyle is Simple.

To get the desired behavior, create and use your own ComboBox subclass like this

public class MyComboBox : ComboBox
{
    protected override bool IsInputKey(Keys keyData)
    {
        if (DropDownStyle == ComboBoxStyle.Simple)
        {
            switch (keyData & (Keys.KeyCode | Keys.Alt))
            {
                case Keys.Return:
                case Keys.Escape:
                    return false;
            }
        }
        return base.IsInputKey(keyData);
    }
}

P.S. And of course don't forget to remove your KeyDown event handlers.



回答2:

While I have no idea about the main reason behind this behavior.

But in this situation obviously KeyDown event triggers 2 times. (Set a breakpoint and you will see.)

Since you need to handle it in code, You can try this to neglect one of Enter keys:

bool handled = true;
private void comboBox1_KeyDown(object sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.Enter)
    {
        /*Prevent handling the Enter key twice*/
        handled = !handled;
        if(handled)
            return;

        //Rest of logic
        //OkButton.PerformClick();
    }
}