Capturing a key without focusing the window

2019-05-05 18:42发布

问题:

I have a application that always checks if a key like F12 is pressed. It doesn't need to have in focus of my main window of my app. I tried this code:

public int a = 1;
    // DLL libraries used to manage hotkeys
    [DllImport("user32.dll")]
    public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc);
    [DllImport("user32.dll")]
    public static extern bool UnregisterHotKey(IntPtr hWnd, int id);

    const int MYACTION_HOTKEY_ID = 1;

    public Form1()
    {
        InitializeComponent();
        // Modifier keys codes: Alt = 1, Ctrl = 2, Shift = 4, Win = 8
        // Compute the addition of each combination of the keys you want to be pressed
        // ALT+CTRL = 1 + 2 = 3 , CTRL+SHIFT = 2 + 4 = 6...
        RegisterHotKey(this.Handle, MYACTION_HOTKEY_ID, 0, (int) Keys.F12);
    }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == 0x0312 && m.WParam.ToInt32() == MYACTION_HOTKEY_ID)
        {

            a++;
            MessageBox.Show(a.ToString());
        }
        base.WndProc(ref m);
    }

I put 0 to this line RegisterHotKey(this.Handle, MYACTION_HOTKEY_ID, 0, (int) Keys.F12); so that only if F12 is pressed it will capture.

But it didn't work. How can I solve this?

Here I couldn't understand some lines like:

const int MYACTION_HOTKEY_ID = 1;
m.Msg == 0x0312 && m.WParam.ToInt32() == MYACTION_HOTKEY_ID
base.WndProc(ref m);

Can anyone help me to understand these lines?

回答1:

Your code has no wrong . But it doesn't work here because the F12 key is reserved you may try with another key like F10, F11 etc .



回答2:

But it didn't work. How can I solve this?

What do you mean "it didn't work"? The code in your question looks correct to me.

The only reason it might not be working is because the RegisterHotKey function is returning an error and you're not checking for it. To make this work, you need to add the SetLastError attribute to its declaration, which causes the runtime to cache the Win32 error code that it sets. Once this is done, you can check that error code (if the function returns false) by calling the GetLastWin32Error function. I recommend using the result of this function to generate and throw a Win32Exception.

Modify your declaration of RegisterHotKey as follows:

[DllImport("user32.dll", PreserveSig = false)]
public static extern bool RegisterHotKey(IntPtr hWnd,
                                         int id,
                                         uint fsModifiers,
                                         Keys key);

And your call to the function as follows:

if (!RegisterHotKey(this.Handle, MYACTION_HOTKEY_ID, 0, Keys.F12))
{
   throw new Win32Exception(Marshal.GetLastWin32Error());
}

Once that's done, I suspect you'll see an exception getting thrown with the error message:

Hot key is already registered

Well, that makes debugging the problem much simpler, now doesn't it! Chances are you'll need to select a different hot key, since the RegisterHotKey function documentation tells us explicitly that:

The F12 key is reserved for use by the debugger at all times, so it should not be registered as a hot key. Even when you are not debugging an application, F12 is reserved in case a kernel-mode debugger or a just-in-time debugger is resident.

When I run the code and register F11 as a hotkey, it works just fine for me.


Here I couldn't understand some lines like:

const int MYACTION_HOTKEY_ID = 1;
m.Msg == 0x0312 && m.WParam.ToInt32() == MYACTION_HOTKEY_ID
base.WndProc(ref m);

Can anyone help me to understand these lines?

Sure:

  1. The first line declares a constant value that uniquely identifies the hotkey that you installed using the RegisterHotKey function. More specifically, it corresponds to the id parameter of the function. You passed it in on the initial call.

  2. This checks in the window procedure (WndProc) to see if the message (Msg) that is being processed is the WM_HOTKEY message. The WM_HOTKEY message is posted automatically to your window whenever the hot key you registered with the RegisterHotKey function is pressed.

    You shouldn't really be using the magic number 0x0312 directly, though, because you're not the only one who is unsure what it means. Instead, define a constant and use that instead:

    const int WM_HOTKEY = 0x0312;
    m.Msg == WM_HOTKEY
    

    The second part of that conditional test (the part after the &&) checks the wParam field of the message to see if the hot key that was pressed was the one you registered. Remember that MYACTION_HOTKEY_ID is the unique ID of your hot key. The WM_HOTKEY message documentation tells us that checking the wParam is how we determine which hot key was pressed.

  3. This calls the base class's window procedure. In other words, what you've done is overridden the virtual WndProc method, allowing you to add some additional code (your processing of WM_HOTKEY). When you're done with your additional logic, you want to continue with the logic of the base class, so you forward the message on.



回答3:

To do a similar thing I implemented a low-level keyboard hook using SetWindowsHookEx. That will trap all keyboard messages going through Windows, and allow you to inspect them and if necessary, prevent them going any further.

Take a look at my KeyboardHandling project in my RocketLauncher GitHub hobby project. You can take what you need directly from that. I'm going to make it a nuget package soon, too.



回答4:

I don't know why, but I feel like this is related to this question... so I'm gonna try to explain it once more:

const int MYACTION_HOTKEY_ID = 1;

is where you save the integer that is used to identify the hotkey. If you need to register more than on hotkey, you will have to declare other integer fields identifying the other hotkeys:

const int ANOTHER_ACTION_HOTKEY_ID = 2;
const int AND_ANOTHER_ACTION_HOTKEY_ID = 3;

Then,

m.Msg == 0x0312 && m.WParam.ToInt32() == MYACTION_HOTKEY_ID

is the condition that enables you to know which hotkey has been typed by the user.

0x0312 (also declared as WM_HOTKEY in the documentation you can found here for instance) is to know if a registered hotkey has been pressed:

When a key is pressed, the system looks for a match against all hot keys. Upon finding a match, the system posts the WM_HOTKEY message to the message queue of the window with which the hot key is associated. If the hot key is not associated with a window, then the WM_HOTKEY message is posted to the thread associated with the hot key.


According to the documentation, you cannot use the F12 hotkey:

The F12 key is reserved for use by the debugger at all times, so it should not be registered as a hot key. Even when you are not debugging an application, F12 is reserved in case a kernel-mode debugger or a just-in-time debugger is resident.