6

Pressing a key in a text box, the KeyDown event occurs before KeyPress.

I used a count and message boxes to see what would happen.

The following is my code:

int Ncount = 0;
private void Textbox_KeyDown(object sender, KeyEventArgs e)
{
    Ncount += 1;
    MessageBox.Show("KeyDown's Ncount : " + Ncount.ToString());
}

private void Textbox_KeyPress(object sender, KeyPressEventArgs e)
{
    Ncount += 1;
    MessageBox.Show("KeyPress's Ncount : " + Ncount.ToString());
}

When a key is pressed, this will show first...

KeyPress's Ncount : 2

...followed by this:

KeyDown's Ncount : 1

Shouldn't the KeyDown message box (with NCount 1) show before the KeyPress message box (with Ncount 2)?

J0e3gan
  • 8,740
  • 10
  • 53
  • 80
  • 4
    I guess all MessageBox.Show calls go in a stack and are called as soon as Windows has one ready. This would mean the first window is at the bottom of the stack and the second one pops first. this is not verified, just a guess. – Philippe Paré Aug 15 '15 at 01:11
  • Using Ncount is just want to make sure KeyDown happens before KeyPress. Ncount is initialized to zero. – edwhere0963 Aug 15 '15 at 01:32
  • Well I would simply put a breakpoint in both, see which one gets called first – Philippe Paré Aug 15 '15 at 01:32
  • Other possibility, It may be due to the GUI thread was busy between these two calls (KeyDown & KeyPress). But not sure. – NASSER Aug 15 '15 at 01:49

3 Answers3

3

Short version: MessageBox.Show() is the infamous Application.DoEvents wolf in sheep's clothing. Definitely more benign than DoEvents but it doesn't solve re-entrancy problems like this one. Always use the Debug class in .NET if you want to display debug info.

Longer version: to make sense of this behavior you first have to know a little factoid: by the time you get the KeyDown event, the operating system has already generated the KeyPress notification. It is sitting patiently in the message queue, waiting for your app to resume the dispatcher loop. It will be the next event you get. Unless you set e.Handled to true, then Winforms looks through the message queue and purges the KeyPress notification so the event won't fire.

Next factoid: in order for MessageBox to become modal, or for that matter any ShowDialog() call, it needs to run a dispatcher loop itself. Which ensures that basic stuff still happens, like painting the windows and MessageBox recognizing the OK button click and the user getting slapped with a Ding! when he clicks on anything else but the message box.

Maybe you can connect the dots now, that dispatcher loop in MessageBox will see the KeyPress notification in the queue. And cause your KeyPress event handler to run. So you display another message box, necessarily it is on top of the first one.

Nothing goes dramatically wrong here, the side-effect is just that the Z-order of the boxes is not the one you expect. You'll get a lot more drama if you set e.Handled = true and expect it to work. It won't. It can't. It was already handled by the time your KeyDown event handler completes.

There is no simple fix for this. But one, don't use it. Always use the Debug class to generate debug info. MessageBox has entirely too many side-effects.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
2

A thread on MSDN forums speaks to this oddity:

The event handlers are running in a separate thread. Modal forms are modal in the context of their own threads.

So whereas message boxes are modal in the form's UI thread, this is not the case for message boxes shown in event handlers.

The message boxes do show in the expected order; they just are not modal per the note above: so they appear to be in the opposite order (when the second/KeyPress message box stacks on top of and covers the first/KeyDown message box).

A variant of your sample code demonstrates what I mean:

int Ncount = 0;

private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
    Ncount += 1;
    var message =
        String.Format(
            "({0}) KeyDown's Ncount : {1}",
            DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture),
            Ncount);
    Debug.WriteLine(message);
    MessageBox.Show(message);
}

private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
    Ncount += 1;
    var message =
        String.Format(
            "({0}) KeyPress's Ncount : {1}",
            DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture),
            Ncount);
    Debug.WriteLine(message);
    MessageBox.Show(message);
}

It yields the following console output...

(2015-08-15 03:45:31.455) KeyDown's Ncount : 1
(2015-08-15 03:45:31.487) KeyPress's Ncount : 2

...and message boxes in the following unexpected order it would seem...

KeyPress Message Box

KeyDown Message Box

...but really in the opposite/expected order as the time stamps in the message boxes show.

J0e3gan
  • 8,740
  • 10
  • 53
  • 80
1

A KeyPressEventArgs specifies the character that is composed when the user presses a key. For example, when the user presses SHIFT + K, the KeyChar property returns an uppercase K.

A KeyPress event occurs when the user presses a key. Two events that are closely related to the KeyPress event are KeyUp and KeyDown. The KeyDown event precedes each KeyPress event when the user presses a key, and a KeyUp event occurs when the user releases a key. When the user holds down a key, duplicate KeyDown and KeyPress events occur each time the character repeats. One KeyUp event is generated upon release.

With each KeyPress event, a KeyPressEventArgs is passed. A KeyEventArgs is passed with each KeyDown and KeyUp event. A KeyEventArgs specifies whether any modifier keys (CTRL, SHIFT, or ALT) were pressed along with another key. (This modifier information can also be obtained through the ModifierKeys property of the Control class.)

Set Handled to true to cancel the KeyPress event. This keeps the control from processing the key press."

However also note -

"Some controls will process certain key strokes on KeyDown. For example, RichTextBox processes the Enter key before KeyPress is called. In such cases, you cannot cancel the KeyPress event, and must cancel the key stroke from KeyDown instead."

The documentation is clear that KeyDown fires before KeyPress and you can show that it does by coding something like this:

Private Sub TextBox1_KeyDown(sender As Object, e As KeyEventArgs) _
  Handles TextBox1.KeyDown
    Debug.Print("Down")
    MessageBox.Show("Down")
    e.Handled = True
End Sub

Private Sub TextBox1_KeyPress(sender As Object, e As KeyPressEventArgs) _
  Handles TextBox1.KeyPress
    Debug.Print("Press")
    MessageBox.Show("Press")
End Sub

Running this you will see that Debug writes "Down" before "Press". You can also but breakpoints in the two handlers and see that KeyDown fires before KeyPress.

You will also see that the "Press" MessageBox shows before the "Down" MessageBox. That is curious and I would be interested if someone could explain it.

Reference: KeyPress Event firing before KeyDown Event on textbox.

NASSER
  • 5,900
  • 7
  • 38
  • 57
  • While this may answer the question if that link ever goes dead your answer becomes useless. At lest summarize the information in the link so that a person would have enough information to google it and get the info they need. – Scott Chamberlain Aug 15 '15 at 02:00