在 Winforms 中,PreviewKeyDown() 从不为任何键触发 [英] In Winforms, PreviewKeyDown() never fired for ANY key

查看:60
本文介绍了在 Winforms 中,PreviewKeyDown() 从不为任何键触发的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最初试图让我的程序获得箭头键(向上、向下、向左和向右)的输入,但发现在 KeyDown() 中,这些键从未出现过.后来我发现我可以通过进入 PreviewKeyDown() 函数和设置来启用箭头键:

I was originally trying to get my program to get inputs of the arrow keys (Up, Down, Left and Right), but found out the hard way that in KeyDown(), those keys never made. Afterwards I found out that I could enable the arrow keys by going into the PreviewKeyDown() function and setting:

e.IsInputKey = true;

带有任何条件和逻辑.问题是我写函数的时候:

with whatever conditionals and logic around it. The trouble was that when I wrote the function:

private void Form1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{ /*whatever logic goes here*/}

它从未开火;我什至设置了一个断点,以确保在函数内部触发.另外,我试过:

it never fired; I even set a breakpoint that would trigger inside the function to be sure. Also, I tried:

this.Focus()

在构造函数中确保主窗体具有焦点,但没有任何区别.唯一有效的是将焦点设置到我创建的 Button 上,并且该按钮还通过调用上面的 Form1_PreviewKeyDown() 触发 PreviewKeyDown 事件.

in the constructor to make sure that the main form had the focus, but it made no difference. The only thing that worked was setting the focus to a Button I had created and the button also trigger on a PreviewKeyDown event by calling the above Form1_PreviewKeyDown().

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

So at this point I have a working method, but can anyone help me understand why it never originally fired? I'm assuming that for some reason the Form's PreviewKeyEvent never fires, but I really have no idea why.

推荐答案

为什么

你可以试试这个小实验:制作一个有两个按钮的表单,覆盖PreviewKeyDown(),设置一个断点,运行它,然后按左/右箭头键.PreviewKeyDown() 方法不会运行.但是删除按钮和覆盖被调用.

Why

You can try this little experiment: Make a form with two buttons, override PreviewKeyDown(), set a breakpoint, run it, and press the left/right arrow keys. The PreviewKeyDown() method won't be run. But delete the buttons and the override will be called.

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

The reason for the difference is that WinForms is handling the arrow keys itself for navigation. When you have input controls like buttons and text boxes, WinForms will automatically take over certain special keys like TAB and the arrow keys to navigate from one control to the next. It probably does this because a lot of people like to be able to use the keyboard to navigate, and it's easy to break that for them if you go messing with the navigation keys. Better to handle them for you so you don't mess them up by accident while you're playing with the other keys.

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

A naive workaround would be to detect when you form loses focus and take it back. This doesn't work though, because your form doesn't lose focus. The input controls have the focus, and they're part of the form, so the form still (technically, indirectly) has focus. It only loses the focus when you click outside on some other window.

更好的解决方法是更好地了解 .Net 解释器下方幕后"发生的事情.WinForms 非常接近地模拟了这一级别,因此它是了解 WinForms 的有用指南.

A better workaround involves a better understanding of what's going on "under the covers", just below the .Net interpreter. WinForms mimics this level fairly closely, so it's a useful guide to understanding what WinForms is up to.

当 Windows 向您的程序发送输入(如按键)时,您的表单并不总是第一个获得输入.输入转到具有焦点的任何控件.在这种情况下,该控件是一个按钮(我假设首先隐藏焦点发光,以证明为什么在没有选择任何内容时在第一笔画上什么也没有发生).

When Windows sends input (like keystrokes) to your program, your form isn't always the first to get the input. The input goes to whichever control has the focus. In this case, that control is one of the buttons (I'm assuming the focus glow is hidden at first to justify why nothing happens on the first stroke when nothing looks selected).

一旦按钮获得输入,它就可以决定接下来会发生什么.它可以将输入传递给下一个队列,做某事然后然后传递它,或者完全处理输入而不传递它.

Once the button gets hold of the input, it gets to decide what happens next. It can pass the input on to whoever's next in line, do something and then pass it on, or completely handle the input and not pass it on at all.

对于普通的字母键,按钮决定不知道如何处理它们,而是将它们传递给它的基类.基类也不知道,所以它转发密钥.最终,它命中 Control 类,该类通过将其传递给其 Parent 属性中的任何 Control 来处理它.如果这种情况持续足够长,您的表单最终将有机会处理输入.

With normal letter keys, the button decides it doesn't know what to do with them and passes them to its base class instead. The base class doesn't know either, so it forwards the key on. Eventually, it hits the Control class, which handles it by passing it on to whichever Control is in its Parent property. If that goes on long enough, your form will eventually get a chance to handle the input.

因此,简而言之,WinForms 首先将输入提供给最具体的目标,然后再处理越来越普遍的事情,直到有人知道如何处理输入.

So in a nutshell, WinForms is giving the input to the most specific target first, then working out to more and more general things until someone knows how to handle the input.

然而,在箭头键的情况下,按钮知道如何处理这些.它通过将焦点传递到下一个输入控件来处理它们.在这一点上,按钮声明输入完全处理,吞下键并且不给其他任何人看它的机会.按钮之后没有人知道击键发生过.

In the case of the arrow keys, however, the button knows how to handle those. It handles them by passing the focus on to the next input control. At that point, the button declares the input totally handled, swallows the key and doesn't give anyone else a chance to look at it. Nobody after the button even knows the keystroke ever happened.

这就是没有调用您的 PreviewKeyDown() 覆盖的原因.它仅在您的 Form 获得击键时调用,但它永远不会获得击键,因为它转到输入控件,输入控件提供让导航代码查看它,而导航代码吞下了它.

That's why your PreviewKeyDown() override isn't being called. It's only called when your Form gets a keystroke, but it never gets the keystroke because it went to an input control, the input control offered to let the navigation code look at it, and the navigation code swallowed it.

不幸的是,解决这个问题需要一些工作.击键正在消失在输入控件中,因此您需要获取将箭头键输入表单所涉及的所有输入控件.

Unfortunately, getting around this is going to be some work. The keystrokes are disappearing into the input controls, so you'll need to get all the input controls involved in getting the arrow keys into your form.

为此,您需要从您使用的所有输入控件类型中派生出新控件,并使用它们代替原始控件.然后,您必须覆盖每个方法中的 OnPreviewKeyDown() 方法并设置 e.IsInputKey = true.这将使您的箭头键进入派生控件的 KeyDown() 处理程序,而不是让它们被导航代码窃取.

To do this, you'll need to derive new controls from all the input control types you use and use them in place of the originals. Then you'll have to override the OnPreviewKeyDown() method in each one and set e.IsInputKey = true. That'll get your arrow keys into the derived controls' KeyDown() handlers instead of having them stolen by the navigation code.

接下来,您还必须处理所有这些控件中的 KeyDown() 事件.由于您希望箭头键在 Form 中引发事件,所有派生控件都需要跟踪它们的表单并将键传递给它(这意味着表单的方法需要是公共的).

Next, you'll have to handle the KeyDown() event in all those controls, too. Since you want the arrow keys to raise events in the Form, all the derived controls will need to track down their form and pass the keys to that (which means the form's method will need to be public).

将所有这些放在一起,箭头键传递输入控件将如下所示.

Putting all that together, the arrow-key-passing input controls will look about like this.

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);
    }
}

所有重复的代码都容易出错.

That's going to be a bit error prone with all the repeated code.

更简单的方法是覆盖表单的 ProcessCmdKey() 方法并处理那里的键.这样的事情可能会奏效:

An easier way would be to override your form's ProcessCmdKey() method and handle the keys there. Something like this would probably work:

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() 不会被调用.

This effectively steals the command keys (those special navigation keys) even before the input controls get a chance at them. Unless those controls override PreviewKeyDown() and set e.IsInputKey = true. The child's PreviewKeyDown() method will come first, then the arrow will be considered not a command key and your ProcessCmdKey() won't be called.

ProcessCmdKey() 适用于 上下文菜单处理.我不确定将它用于上下文菜单以外的东西是否明智,但是 即使是 Microsoft 也推荐它用于类似的用法而且似乎确实有效,因此可能值得考虑.

ProcessCmdKey() is meant for context menu handling. I'm not sure whether it's wise to go using it for things other than context menus, but even Microsoft recommends it for similar kinds of use and it does seem to work, so it may be worth considering.

长话短说,导航键用于导航.弄乱它们会使键盘用户的用户体验不愉快,因此 .Net 很难找到它们,因此会鼓励您改用其他键.

Long story short, navigation keys are meant for navigation. Messing with them can make the user experience unpleasant for keyboard users, so .Net makes it hard to get at them so you'll be encouraged to mess with other keys instead.

这篇关于在 Winforms 中,PreviewKeyDown() 从不为任何键触发的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆