我原本试图让我的程序获得箭头键的输入(向上,向下,向左和向右),但发现在KeyDown()中,这些键永远不会发生.之后我发现我可以通过进入PreviewKeyDown()函数并设置来启用箭头键:
e.IsInputKey = true;
Run Code Online (Sandbox Code Playgroud)
无论周围的条件和逻辑.麻烦的是当我写这个函数时:
private void Form1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{ /*whatever logic goes here*/}
Run Code Online (Sandbox Code Playgroud)
它从未被解雇; 我甚至设置了一个断点,可以在函数内部触发来确定.另外,我试过:
this.Focus()
Run Code Online (Sandbox Code Playgroud)
在构造函数中,以确保主窗体具有焦点,但它没有任何区别.唯一有效的方法是将焦点设置为我创建的Button,并且该按钮还通过调用上面的Form1_PreviewKeyDown()触发PreviewKeyDown事件.
所以在这一点上我有一个工作方法,但任何人都可以帮助我理解为什么它从未被解雇过?我假设由于某种原因,Form的PreviewKeyEvent永远不会触发,但我真的不知道为什么.
Mir*_*nth 10
您可以尝试这个小实验:使用两个按钮创建一个表单,覆盖PreviewKeyDown(),设置断点,运行它,然后按左/右箭头键.该PreviewKeyDown()方法将不会运行.但删除按钮,将调用覆盖.
造成差异的原因是WinForms正在处理箭头键本身以进行导航.当你有像按钮和文本框这样的输入控件时,WinForms会自动接管某些特殊键,例如TAB箭头键,从一个控件导航到下一个控件.它可能是这样做的,因为很多人都喜欢能够使用键盘进行导航,如果你搞乱导航键,很容易打破它们.最好为你处理它们,这样你就不会在玩其他键时意外搞乱它们.
一个天真的解决方法是检测何时形成失去焦点并将其收回.但这不起作用,因为您的表单不会失去焦点.输入控件具有焦点,它们是表单的一部分,因此表单仍然(技术上,间接地)具有焦点.当您在其他窗口上单击外部时,它只会失去焦点.
一个更好的解决方法是更好地理解"在幕后"发生的事情,就在.Net解释器下面.WinForms非常接近地模仿这个级别,因此它是了解WinForms最新功能的有用指南.
当Windows向您的程序发送输入(如击键)时,您的表单并不总是第一个获得输入.输入转到任何具有焦点的控件.在这种情况下,该控件是其中一个按钮(我假设首先隐藏焦点发光,以证明为什么在没有任何外观选择的情况下第一个笔划没有发生任何事情).
一旦按钮获得输入,它就可以决定接下来会发生什么.它可以将输入传递给下一行的任何人,做某事然后传递它,或者完全处理输入而不是传递它.
使用普通的字母键,按钮决定它不知道如何处理它们并将它们传递给它的基类.基类也不知道,所以它转发密钥.最终,它击中了Control类,它通过将它传递给Control它的Parent属性中的任何一个来处理它.如果这种情况持续很长时间,您的表单最终将有机会处理输入.
简而言之,WinForms首先将输入提供给最具体的目标,然后研究越来越多的一般事物,直到有人知道如何处理输入.
但是,在箭头键的情况下,按钮知道如何处理它们.它通过将焦点传递给下一个输入控件来处理它们.此时,按钮声明输入完全处理,吞下键并且不会让任何其他人有机会查看它.按钮后面的任何人都不知道按键发生了.
这就是你PreviewKeyDown()没有调用你的覆盖的原因.只有当你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);
}
}
Run Code Online (Sandbox Code Playgroud)
所有重复的代码都会有点容易出错.
一种更简单的方法是覆盖表单的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);
}
Run Code Online (Sandbox Code Playgroud)
这甚至在输入控件获得机会之前就有效地窃取了命令键(那些特殊的导航键).除非这些控件覆盖PreviewKeyDown()和设置e.IsInputKey = true.孩子的PreviewKeyDown()方法将首先出现,然后箭头将被视为不是命令键,您ProcessCmdKey()将不会被调用.
ProcessCmdKey()用于上下文菜单处理.我不确定将它用于上下文菜单之外的其他事情是否明智,但即使微软推荐它用于类似的用途,它似乎也有用,所以它可能值得考虑.
长话短说,导航键用于导航.与它们混淆可能会使键盘用户的用户体验变得不愉快,因此.Net使得很难找到它们,因此您将被鼓励混淆其他键.
小智 5
我有同样的问题!
幸运的是,我找到了一个密集的答案:)
您可以在Form类的定义中使用bool函数,每按一次键都会发生一次。但是请记住返回基本函数!
public partial class myForm : Form
{
public myForm ()
{
InitializeComponent();
}
protected override bool ProcessDialogKey(Keys keyData)
{
//Add your code here
return base.ProcessDialogKey(keyData);
}
}
Run Code Online (Sandbox Code Playgroud)
希望我有所帮助。但是,如果我的回答不完整,请注意我!