Why
您可以尝试这个小实验:制作一个带有两个按钮的表单,覆盖PreviewKeyDown()
,设置断点,运行,然后按左/右箭头键。这PreviewKeyDown()
方法将不会运行。但删除按钮和覆盖will叫做。
造成差异的原因是 WinForms 本身处理箭头键以进行导航。当您有按钮和文本框等输入控件时,WinForms 将自动接管某些特殊键,例如TAB
和箭头键从一个控件导航到下一个控件。这样做可能是因为很多人喜欢能够使用键盘进行导航,如果您弄乱导航键,很容易破坏他们的功能。最好为您处理它们,这样您在玩其他键时就不会意外弄乱它们。
一个简单的解决方法是检测您何时失去焦点并将其收回。但这不起作用,因为您的表单不会失去焦点。输入控件具有焦点,并且它们是表单的一部分,因此表单仍然(技术上,间接地)具有焦点。仅当您在其他窗口上单击外部时,它才会失去焦点。
更好的解决方法包括更好地理解 .Net 解释器下面“幕后”发生的事情。 WinForms 相当接近地模仿了这个级别,因此它是了解 WinForms 功能的有用指南。
当 Windows 将输入(如击键)发送到您的程序时,您的窗体并不总是第一个获取输入的。输入将转到具有焦点的控件。在这种情况下,该控件是其中一个按钮(我假设焦点发光首先被隐藏,以证明为什么当看起来没有选择任何内容时,第一次笔画没有发生任何事情)。
一旦按钮获得输入,它就可以决定接下来发生什么。它可以将输入传递给下一个排队的人,做一些事情并then传递它,或者完全处理输入并且根本不传递它。
对于普通的字母键,按钮决定不知道如何处理它们,而是将它们传递给其基类。基类也不知道,因此它将密钥转发。最终,它击中了Control
类,它通过将其传递给任何一个来处理它Control
是在其Parent
财产。如果这种情况持续足够长的时间,您的表单最终将有机会处理输入。
简而言之,WinForms 首先将输入提供给最具体的目标,然后处理越来越普遍的事情,直到有人知道如何处理输入。
然而,在使用箭头键的情况下,按钮知道如何处理这些。它通过将焦点传递到下一个输入控件来处理它们。此时,按钮声明输入已完全处理,吞下密钥并且不给其他任何人查看它的机会。按下按钮后没有人知道击键发生过。
这就是为什么你的PreviewKeyDown()
没有调用 override。仅当您的Form
获取击键,但它永远不会获取击键,因为它进入输入控件,输入控件提供让导航代码查看它,而导航代码吞掉了它。
解决方法
不幸的是,解决这个问题需要一些工作。击键消失在输入控件中,因此您需要获取将箭头键放入表单中涉及的所有输入控件。
为此,您需要从您使用的所有输入控件类型派生新控件,并使用它们代替原始控件。然后你必须重写OnPreviewKeyDown()
每一项和集合中的方法e.IsInputKey = true
。这将使您的箭头键进入派生控件'KeyDown()
处理程序,而不是让它们被导航代码窃取。
接下来,您必须处理KeyDown()
所有这些控件中也有事件。由于您希望箭头键在Form
,所有派生控件都需要跟踪其表单并将密钥传递给该表单(这意味着表单的方法需要公开)。
将所有这些放在一起,箭头键传递输入控件将如下所示。
class MyButton : Button
{
public MyButton()
{
this.KeyDown += new KeyEventHandler(MyButton_KeyDown);
}
protected override void OnPreviewKeyDown(PreviewKeyDownEventArgs e)
{
e.IsInputKey = true;
base.OnPreviewKeyDown(e);
}
private void MyButton_KeyDown(object sender, KeyEventArgs e)
{
Form1 f = (Form1)this.FindForm();
f.Form1_KeyDown(sender, e);
}
}
所有重复的代码都会有点容易出错。
一种更简单的方法是覆盖您的表单ProcessCmdKey()
方法并处理那里的键。像这样的东西可能会起作用:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Up || keyData == Keys.Down ||
keyData == Keys.Left || keyData == Keys.Right)
{
object sender = Control.FromHandle(msg.HWnd);
KeyEventArgs e = new KeyEventArgs(keyData);
Form1_KeyPress(sender, e);
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
这甚至在输入控件有机会获取命令键(那些特殊的导航键)之前就有效地窃取了它们。除非这些控件覆盖PreviewKeyDown()
并设置e.IsInputKey = true
。孩子的PreviewKeyDown()
方法将首先出现,然后箭头将被视为不是命令键,并且您的ProcessCmdKey()
不会被调用。
ProcessCmdKey()
是为了上下文菜单处理 http://msdn.microsoft.com/en-us/library/vstudio/system.windows.forms.control.processcmdkey%28v=vs.100%29.aspx。我不确定将它用于上下文菜单以外的其他用途是否明智,但是甚至微软也推荐它用于类似的用途 http://support.microsoft.com/kb/320584而且它似乎确实有效,因此可能值得考虑。
结论
长话短说,导航键是用来导航的。弄乱它们可能会给键盘用户带来不愉快的用户体验,因此 .Net 使弄乱它们变得困难,因此我们会鼓励您去弄乱其他键。