为什么 KeyPress 的消息框显示在 KeyDown 之前?
Why does KeyPress's message box show before KeyDown's?
在文本框中按下一个键,KeyDown 事件发生在 KeyPress 之前。
我使用计数和消息框来查看会发生什么。
以下是我的代码:
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());
}
按下某个键时,首先显示...
KeyPress's Ncount : 2
...接着是:
KeyDown's Ncount : 1
KeyDown 消息框(NCount 为 1)不应该显示在 KeyPress 消息框(Ncount 为 2)之前吗?
KeyPressEventArgs 指定用户按下某个键时组成的字符。例如,当用户按下 SHIFT + K 时,KeyChar 属性 returns 一个大写的 K.
当用户按下某个键时会发生 KeyPress 事件。与 KeyPress 事件密切相关的两个事件是 KeyUp 和 KeyDown。当用户按下一个键时,KeyDown 事件先于每个 KeyPress 事件,而当用户释放一个键时,KeyUp 事件发生。当用户按住某个键时,每次重复该字符时都会发生重复的 KeyDown 和 KeyPress 事件。释放时产生一个 KeyUp 事件。
对于每个 KeyPress 事件,都会传递一个 KeyPressEventArgs。 KeyEventArgs 随每个 KeyDown 和 KeyUp 事件传递。 KeyEventArgs 指定是否有任何修改键(CTRL、SHIFT 或 ALT)与另一个键一起按下。 (这个修改器信息也可以通过Controlclass的ModifierKeys属性获取。)
将 Handled 设置为 true 以取消 KeyPress 事件。这样可以防止控件处理按键。"
不过还要注意-
"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."
文档清楚地表明 KeyDown 在 KeyPress 之前触发,您可以通过编写如下代码来证明它确实如此:
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
运行 这个你会看到Debug在"Press"之前写了"Down"。您也可以在两个处理程序中设置断点,然后看到 KeyDown 在 KeyPress 之前触发。
您还会看到 "Press" MessageBox 显示在 "Down" MessageBox 之前。这很好奇,如果有人能解释一下,我会很感兴趣。
一个thread on MSDN forums谈到了这个怪事:
The event handlers are running in a separate thread. Modal forms are
modal in the context of their own threads.
因此,尽管消息框在表单的 UI 线程中是模态的,但事件处理程序中显示的消息框并非如此。
消息框 按预期顺序显示;根据上面的注释,它们只是不是模态的:所以它们 出现 的顺序相反(当 second/KeyPress 消息框堆叠在 first/KeyDown 消息框)。
您的示例代码的一个变体演示了我的意思:
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);
}
它产生以下控制台输出...
(2015-08-15 03:45:31.455) KeyDown's Ncount : 1
(2015-08-15 03:45:31.487) KeyPress's Ncount : 2
...和以下意想不到的顺序中的消息框看起来...
...但实际上 在 opposite/expected 顺序中,如消息框中的时间戳所示。
简短版本:MessageBox.Show() 是臭名昭著的 Application.DoEvents 披着羊皮的狼。绝对比 DoEvents 更温和,但它不能解决像这样的重入问题。如果要显示调试信息,请始终在 .NET 中使用 Debug
class。
更长的版本:要理解此行为,您首先必须知道一些事实:在您收到 KeyDown 事件时,操作系统已经生成了 KeyPress 通知。它耐心地坐在消息队列中,等待您的应用程序恢复调度程序循环。这将是您获得的下一个事件。除非您将 e.Handled
设置为 true,否则 Winforms 会查看消息队列并清除 KeyPress 通知,这样事件就不会触发。
下一个事实:为了使 MessageBox 成为模态,或者就此而言任何 ShowDialog() 调用,它需要 运行 调度程序循环本身。这确保基本的事情仍然发生,比如绘制 windows 和 MessageBox 识别 OK 按钮点击和用户被 Ding 打耳光!当他单击消息框以外的任何其他内容时。
也许您现在可以将这些点联系起来,MessageBox 中的调度程序循环将在队列中看到 KeyPress 通知。并使您的 KeyPress 事件处理程序变为 运行。所以你显示另一个消息框,它必然在第一个消息框之上。
这里没有 戏剧性的 错误,副作用只是盒子的 Z 顺序不是您期望的那样。如果你设置 e.Handled = true 并期望它起作用,你会得到更多的戏剧性。它不会。它不能。它已经在您的 KeyDown 事件处理程序完成时处理。
对此没有简单的解决方法。但是,一个,不要使用它。始终使用 Debug class 生成调试信息。 MessageBox 的副作用太多了。
在文本框中按下一个键,KeyDown 事件发生在 KeyPress 之前。
我使用计数和消息框来查看会发生什么。
以下是我的代码:
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());
}
按下某个键时,首先显示...
KeyPress's Ncount : 2
...接着是:
KeyDown's Ncount : 1
KeyDown 消息框(NCount 为 1)不应该显示在 KeyPress 消息框(Ncount 为 2)之前吗?
KeyPressEventArgs 指定用户按下某个键时组成的字符。例如,当用户按下 SHIFT + K 时,KeyChar 属性 returns 一个大写的 K.
当用户按下某个键时会发生 KeyPress 事件。与 KeyPress 事件密切相关的两个事件是 KeyUp 和 KeyDown。当用户按下一个键时,KeyDown 事件先于每个 KeyPress 事件,而当用户释放一个键时,KeyUp 事件发生。当用户按住某个键时,每次重复该字符时都会发生重复的 KeyDown 和 KeyPress 事件。释放时产生一个 KeyUp 事件。
对于每个 KeyPress 事件,都会传递一个 KeyPressEventArgs。 KeyEventArgs 随每个 KeyDown 和 KeyUp 事件传递。 KeyEventArgs 指定是否有任何修改键(CTRL、SHIFT 或 ALT)与另一个键一起按下。 (这个修改器信息也可以通过Controlclass的ModifierKeys属性获取。)
将 Handled 设置为 true 以取消 KeyPress 事件。这样可以防止控件处理按键。"
不过还要注意-
"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."
文档清楚地表明 KeyDown 在 KeyPress 之前触发,您可以通过编写如下代码来证明它确实如此:
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
运行 这个你会看到Debug在"Press"之前写了"Down"。您也可以在两个处理程序中设置断点,然后看到 KeyDown 在 KeyPress 之前触发。
您还会看到 "Press" MessageBox 显示在 "Down" MessageBox 之前。这很好奇,如果有人能解释一下,我会很感兴趣。
一个thread on MSDN forums谈到了这个怪事:
The event handlers are running in a separate thread. Modal forms are modal in the context of their own threads.
因此,尽管消息框在表单的 UI 线程中是模态的,但事件处理程序中显示的消息框并非如此。
消息框 按预期顺序显示;根据上面的注释,它们只是不是模态的:所以它们 出现 的顺序相反(当 second/KeyPress 消息框堆叠在 first/KeyDown 消息框)。
您的示例代码的一个变体演示了我的意思:
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);
}
它产生以下控制台输出...
(2015-08-15 03:45:31.455) KeyDown's Ncount : 1
(2015-08-15 03:45:31.487) KeyPress's Ncount : 2
...和以下意想不到的顺序中的消息框看起来...
...但实际上 在 opposite/expected 顺序中,如消息框中的时间戳所示。
简短版本:MessageBox.Show() 是臭名昭著的 Application.DoEvents 披着羊皮的狼。绝对比 DoEvents 更温和,但它不能解决像这样的重入问题。如果要显示调试信息,请始终在 .NET 中使用 Debug
class。
更长的版本:要理解此行为,您首先必须知道一些事实:在您收到 KeyDown 事件时,操作系统已经生成了 KeyPress 通知。它耐心地坐在消息队列中,等待您的应用程序恢复调度程序循环。这将是您获得的下一个事件。除非您将 e.Handled
设置为 true,否则 Winforms 会查看消息队列并清除 KeyPress 通知,这样事件就不会触发。
下一个事实:为了使 MessageBox 成为模态,或者就此而言任何 ShowDialog() 调用,它需要 运行 调度程序循环本身。这确保基本的事情仍然发生,比如绘制 windows 和 MessageBox 识别 OK 按钮点击和用户被 Ding 打耳光!当他单击消息框以外的任何其他内容时。
也许您现在可以将这些点联系起来,MessageBox 中的调度程序循环将在队列中看到 KeyPress 通知。并使您的 KeyPress 事件处理程序变为 运行。所以你显示另一个消息框,它必然在第一个消息框之上。
这里没有 戏剧性的 错误,副作用只是盒子的 Z 顺序不是您期望的那样。如果你设置 e.Handled = true 并期望它起作用,你会得到更多的戏剧性。它不会。它不能。它已经在您的 KeyDown 事件处理程序完成时处理。
对此没有简单的解决方法。但是,一个,不要使用它。始终使用 Debug class 生成调试信息。 MessageBox 的副作用太多了。