Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

KeyDown event is not firing, KeyPreview set to true

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;
        }
    }
}
like image 370
WonderCsabo Avatar asked Oct 22 '25 23:10

WonderCsabo


2 Answers

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);
}
like image 141
WonderCsabo Avatar answered Oct 24 '25 14:10

WonderCsabo


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.

like image 33
Mohammad Dehghan Avatar answered Oct 24 '25 12:10

Mohammad Dehghan