1

I have a Form that closes itself when the ESC key is pressed, thanks to KeyPreview, ProcessKeyEventArgs, ProcessCmdKey or whatever. But I have a Control on that form that does very relevant things when ESC is pressed (it hides itself) and the Form should not be closed when that happens.

The control uses the KeyDown event and sets the SuppressKeyPress flag to true, but that happens after the aforementioned form key preview, thus having no effect.

Is there some sort KeyPostview ?

How do I not close the form when a Control has a relevant use of a key hit ?

Edit: The control handling ESC is a textbox embedded in a hand-maid ListView. The textbox appears when the user clicks a cell, enabling edition. To validate the new text, ENTER would be nice (that already works, as giving the focus to anything else). To cancel edition, ESC seems most natural.

Gabriel
  • 2,841
  • 4
  • 33
  • 43
  • 2
    I'm somewhat suspicious of the design in the first place. Is the average user going to be comfortable with the Esc key doing two *very* different things? Will they understand the concept of "focus" and the "active control" well enough to be able to expect what will happen? At least make sure you've considered this from the user's perspective, rather than just a developer's. – Cody Gray - on strike Dec 29 '10 at 21:05
  • Think of Excel. When you doubleclick a cell, you get to edit it. ESC cancels the edition. – Gabriel Dec 29 '10 at 23:24
  • 1
    but excel doesn't close when you git Esc as you have described your app doing – David Heffernan Dec 30 '10 at 00:33

4 Answers4

1

OK - this works:

class CustomTB : TextBox
{
    public CustomTB()
        : base()
    {
        CustomTB.SuppressEscape = false;
    }

    public static bool SuppressEscape { get; set; }

    protected override void OnKeyDown(KeyEventArgs e)
    {
        CustomTB.SuppressEscape = (e.KeyCode == Keys.Escape);
        base.OnKeyUp(e);
    }
}

In your form:

    public Form1()
    {
        InitializeComponent();
        this.KeyPreview = true;
    }

    private void Form1_KeyUp(object sender, KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Escape && !CustomTB.SuppressEscape)
        {
            this.Close();
        }
        CustomTB.SuppressEscape = false;
    }
zsalzbank
  • 9,685
  • 1
  • 26
  • 39
  • Yeah, that's what I was thinking. – Moose Dec 29 '10 at 21:10
  • Once again, I don't want to have to check for that particular control in Form_KeyUp, that's too easy to forget. – Gabriel Dec 29 '10 at 21:14
  • my new code works too. if you are worried about forgetting, then make a custom control. – zsalzbank Dec 29 '10 at 21:18
  • This new version looks great! I'll test it right away (: Well, I can't have a variable accessible to both the textbox and the form, as the textbox is embedded in a UserControl, and its PreviewKeyDown event happens in another class, that is, not the form subclass. – Gabriel Dec 29 '10 at 21:23
  • new code works how you want. no need to check every control, it uses a static variable across all the custom controls to keep track. it should work with multiple of the same custom control, because only one can have focus (and therefore fire) at a time – zsalzbank Dec 29 '10 at 21:43
  • That's pretty cool. The solution boiled down to a static member check. \o/ Still have to check it though. I'd have to get my old forms back and add the test. – Gabriel Dec 29 '10 at 22:51
1

You are competing Big Time over the Escape key. Along with the Enter key, that's a very important key in the standard Windows user interface. Just drop a button on form and set the form's CancelButton property to some other button, that will suck the keystroke to that button.

To compete with that, you have to create a control that tells Winforms that you really think that the Escape key is more important. That requires overriding the IsInputKey property. Like this:

using System;
using System.Windows.Forms;

class MyTexBox : TextBox {
    protected override bool IsInputKey(Keys keyData) {
        if (keyData == Keys.Escape) return true;
        return base.IsInputKey(keyData);
    }
    protected override void OnKeyDown(KeyEventArgs e) {
        if (e.KeyData == Keys.Escape) {
            this.Text = "";   // for example
            e.SuppressKeyPress = true;
            return;
        }
        base.OnKeyDown(e);
    }
}
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Works wonders. There are two problems though. 1. I must have a button on the form to handle the CancelButton thing. That's no big deal for me because I already have one on virtuall al my forms, but that may be a hassle for other coders. 2. That button must be both Enabled and Visible. To avoid confusion at design time it should be effectively visible at design time, thus visible at runtime. Hiding it by moving it out of the bounds of the form OnLoad may be another hassle. That's still the best solution for me right now coz it doesn't involve any check to close the form. \o/ – Gabriel Dec 30 '10 at 11:20
  • 1
    Override the form's ProcessCmdKey(). – Hans Passant Dec 30 '10 at 12:48
0

Can you check to see what control has the focus first? If there's only one control on your form that does something relevant with the escape key, check to see if that's the control that has the focus before you close the form.

Moose
  • 5,354
  • 3
  • 33
  • 46
  • I don't want the Form to check for that particular control (which is in fact a UserControl). I'd like to be able to drop that control on the Form and, doing nothing, expect it will work properly. I am, however, willing to change the KeyPreview thing, which looks like a dead end. – Gabriel Dec 29 '10 at 21:06
  • Okay. Seems like 2 lines would do it for you, can't get much simpler than that. I have to agree with Cody above anyway. – Moose Dec 29 '10 at 21:09
0

The basic problem is that the form's Dispose method is called when Close is called, so the form is going to close and there's not much you can do about it.

I would get around this by having the UserControl implement a marker interface, say ISuppressEsc. The form's KeyUp handler can then locate the currently focused control and cancel the close if the focused control implements ISuppressEsc. Be aware that you will have to do extra work to find the focused control if it may be a nested control.

public interface ISuppressEsc
{
    // marker interface, no declarations
}

public partial class UserControl1 : UserControl, ISuppressEsc
{
    public UserControl1()
    {
        InitializeComponent();
    }

    private void textBox1_KeyUp(object sender, KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Escape)
        {
            textBox1.Text = DateTime.Now.ToLongTimeString();
        }
    }
}

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

    private void Form1_KeyUp(object sender, KeyEventArgs e)
    {
        var activeCtl = ActiveControl;
        if (!(activeCtl is ISuppressEsc) && e.KeyCode == Keys.Escape)
        {
            Close();
        }
    }
}
Community
  • 1
  • 1
Jamie Ide
  • 48,427
  • 16
  • 81
  • 117