Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Winforms, PreviewKeyDown() never fired for ANY key

Tags:

c#

winforms

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()

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().

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.

like image 799
Daniel Avatar asked Sep 06 '14 03:09

Daniel


3 Answers

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.

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.

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.

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.

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.

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.

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.

Workaround

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.

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.

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.

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

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() 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.

Conclusion

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.

like image 128
Mirinth Avatar answered Oct 11 '22 15:10

Mirinth


I had the same problem!

Luckily i found a dense answer :)

you can use the bool function in the definition of the Form class witch occurs on every key pressed. but remember to return the base function!

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

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

        return base.ProcessDialogKey(keyData);  
    }
}

hopefully i helped. but if my answer is incomplete please note me!

like image 28
K.N. Avatar answered Oct 11 '22 13:10

K.N.


Keyboard events on the parent form are pretty useless unless you also set

this.KeyPreview = true;

see the MSDN documentation

like image 24
Ben Voigt Avatar answered Oct 11 '22 15:10

Ben Voigt