KeyDown event is not firing, KeyPreview set to true

23,745

Solution 1

I already commented my solution, but I also post it as an answer, so it can be easily found.

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
    switch (keyData)
    {
        case Keys.Left:
            // left arrow key pressed
            return true;
        case Keys.Right:
            // right arrow key pressed
            return true;
        case Keys.Up:
            // up arrow key pressed
            return true;
        case Keys.Down:
            // down arrow key pressed
            return true;
    }

    return base.ProcessCmdKey(ref msg, keyData);
}

Solution 2

If you were on WPF, you could easily catch the required events, because WPF uses routed event system to dispatch events. In winforms, I recomment one of these two ways:

1. Using Application.AddMessageFilter Method:

Define a Message Filter class:

public class KeyMessageFilter : IMessageFilter
{
    private enum KeyMessages
    {
        WM_KEYFIRST = 0x100,
        WM_KEYDOWN = 0x100,
        WM_KEYUP = 0x101,
        WM_CHAR = 0x102,
        WM_SYSKEYDOWN = 0x0104,
        WM_SYSKEYUP = 0x0105,
        WM_SYSCHAR = 0x0106,
    }

    [DllImport("user32.dll")]
    private static extern IntPtr GetParent(IntPtr hwnd);

    // We check the events agains this control to only handle
    // key event that happend inside this control.
    Control _control;

    public KeyMessageFilter()
    { }

    public KeyMessageFilter(Control c)
    {
        _control = c;
    }

    public bool PreFilterMessage(ref Message m)
    {
        if (m.Msg == (int)KeyMessages.WM_KEYDOWN)
        {
            if (_control != null)
            {
                IntPtr hwnd = m.HWnd;
                IntPtr handle = _control.Handle;
                while (hwnd != IntPtr.Zero && handle != hwnd)
                {
                    hwnd = GetParent(hwnd);
                }
                if (hwnd == IntPtr.Zero) // Didn't found the window. We are not interested in the event.
                    return false;
            }
            Keys key = (Keys)m.WParam;
            switch (key)
            {
                case Keys.Left:
                    MessageBox.Show("Left");
                    return true;
                case Keys.Right:
                    MessageBox.Show("Right");
                    return true;
            }
        }
        return false;
    }
}

So you have a class that every message in Windows Forms passes through it. You can do whatever you want with the event. If PreFilterMessage method returns true, it means that the event should not be dispatched to it's respcetive control.

(Note that the values in the Keys enumeration is almost idential to virtual key codes)

Before this works, you have to add it to the application's message filters:

public partial class Form1 : Form
{
    // We need an instance of the filter class
    KeyMessageFilter filter;

    public Form1()
    {
        InitializeComponent();

        filter = new KeyMessageFilter(panel1);
        // add the filter
        Application.AddMessageFilter(filter);
    }

    protected override void OnFormClosed(FormClosedEventArgs e)
    {
        base.OnFormClosed(e);

        // remove the filter
        Application.RemoveMessageFilter(filter);
    }
}

The filter is only active in the lifetime of the Form1.

Notice: This will catch events in any form! If you want it to work for only one form, pass the form to the filter class, and compare its Handle property with m.HWnd in PreFilterMessage

2. Using Windows Hooks:

This is a more advanced and complicated (and low level) approach. And it requires more code. I've wrote a HookManager class that makes the process very simple. I'm gonna publish the class to github and write an article about it.

Solution 3

The reason of the behaviour you observe is that special keys like TAB, UP/DOWN/LEFT/RIGHT ARROW, PAGE UP/DOWN, HOME, END, etc. are often considered "Input Keys" by common controls.

For example, ARROW keys are considered "Input Keys" by the TabControl as these keys allows you to change the selected TabPage. A similar behaviour is present with a multiline TextBox where the ARROWS keys allows you to move the text cursor.

I assume that the Rumba Mainframe control you have does the same thing for the same reasons. You can try overriding it and changing the implementation of the IsInputKey method or handling the PreviewKeyDown event and setting the IsInputKey property to true.

Please see the documentation of the Control.IsInputKey Method and Control.PreviewKeyDown Event for further details

Share:
23,745
WonderCsabo
Author by

WonderCsabo

Currently i am working at Supercharge as an Android Developer, involved in multiple international projects. I am mostly interested in development for mobile platforms. I am contributing to several Android-related open-source projects, but first of all to AndroidAnnotations (i am an official maintainer).

Updated on November 27, 2020

Comments

  • WonderCsabo
    WonderCsabo over 3 years

    I'm building a small Forms application, i've just started it. But i have this problem: if i put a Control to the form, the KeyDown event is not firing. I'm aware of the KeyPreview property, and set it to true. But that didn't helped... :( I also tried to set the focus to the main form, no success either.

    Any thoughts?

    Edit:

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            KeyDown += new KeyEventHandler(Form1_KeyDown);
            this.KeyPreview = true;
        }
    
        void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Left: MessageBox.Show("Left");
                    break;
                case Keys.Right: MessageBox.Show("Right");
                    break;
            }
        }
    }