在Winforms中,PreviewKeyDown()从不为任何键触发

时间:2014-09-06 03:32:48

标签: c# winforms

我原本试图让我的程序获得箭头键(向上,向下,向左和向右)的输入,但是发现在KeyDown()中的那些键从来没有做过。之后我发现我可以通过进入PreviewKeyDown()函数并设置来启用箭头键:

e.IsInputKey = true;

无论条件和逻辑如何。麻烦的是当我写这个函数时:

private void Form1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{ /*whatever logic goes here*/}
它永远不会被解雇;我甚至设置了一个断点,可以在函数内部触发来确定。另外,我试过了:

this.Focus()
在构造函数中

以确保主窗体具有焦点,但它没有任何区别。唯一有效的方法是将焦点设置为我创建的Button,并且该按钮还通过调用上面的Form1_PreviewKeyDown()触发PreviewKeyDown事件。

所以在这一点上我有一个工作方法,但任何人都可以帮助我理解为什么它从未被解雇过?我假设由于某种原因Form的PreviewKeyEvent永远不会触发,但我真的不知道为什么。

3 个答案:

答案 0 :(得分:8)

为什么

您可以尝试这个小实验:使用两个按钮创建一个表单,覆盖PreviewKeyDown(),设置断点,运行它,然后按左/右箭头键。 PreviewKeyDown()方法无法运行。但是删除按钮并重写调用。

区别的原因是WinForms正在处理箭头键本身进行导航。当您有按钮和文本框等输入控件时,WinForms将自动接管某些特殊键,如TAB和箭头键,以便从一个控件导航到下一个控件。它可能是这样做的,因为很多人都喜欢能够使用键盘进行导航,如果你搞乱了导航键,很容易打破它们。最好为你处理它们,这样你就可以在玩其他键的时候不小心弄乱它们。

一个天真的解决方法是检测何时形成失去焦点并将其取回。但这并不起作用,因为你的表格不会失去焦点。输入控件具有焦点,它们是表单的一部分,因此表单仍然(技术上,间接地)具有焦点。当你在其他窗口点击外面时,它只会失去焦点。

更好的解决方法是更好地了解在.Net解释器下面的#34;下面发生的事情。 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);
    }
}

对于所有重复的代码,这会有点容易出错。

更简单的方法是覆盖表单的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()适用于context menu handling。我不确定将它用于上下文菜单之外的其他事情是否明智,但是even Microsoft recommends it for similar kinds of use它确实有效,所以值得考虑。

结论

长话短说,导航键用于导航。与它们混淆可能会使键盘用户的用户体验变得不愉快,因此.Net使得难以接触它们,因此您将被鼓励混淆其他键。

答案 1 :(得分:3)

我遇到了同样的问题!

幸运的是,我找到了一个密集的答案:)

你可以在每个键按下的Form类的定义中使用bool函数。但请记住返回基本功能!

public partial class myForm : Form 
{
    public myForm ()
    {
        InitializeComponent();
    }

    protected override bool ProcessDialogKey(Keys keyData)  
    {
        //Add your code here

        return base.ProcessDialogKey(keyData);  
    }
}
希望我有所帮助。但如果我的答案不完整请注意我!

答案 2 :(得分:2)

除非你也设置了

,否则父表单上的键盘事件是没用的
this.KeyPreview = true;

请参阅MSDN documentation