Search code examples
visual-c++mfchotkeys

What is the right way to setup a global hot key in MFC app?


I know how to use the accelerator table to setup hot keys within my app. But how do I get a hot key at global level?

I.e. My app is running, but minimised. Yet I still want it to detect and process a hot key.


Solution

  • If you need a system-wide hotkey then you should use RegisterHotKey, passing in the window handle whose window procedure is responsible for handling the event. At the application level this is commonly the CFrameWnd/CFrameWndEx-derived window implementation.

    Once the hotkey is registered, the receiving window can observe the event in its custom CWnd::OnHotKey override. Make sure to add the ON_WM_HOTKEY() message handler to the message map of the receiving window implementation.

    For a standard, default-generated SDI/MDI application you'd need to apply the following changes.

    MainFrm.h:

    class CMainFrame : public CFrameWnd
    {
        // ...
    private:
        // Hot key handler routine
        afx_msg void OnHotKey(UINT nHotKeyId, UINT nKey1, UINT nKey2);
    };
    

    MainFrm.cpp:

    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
        // ...
    
        // Make sure that WM_HOTKEY messages are routed to this window
        ON_WM_HOTKEY()
    END_MESSAGE_MAP()
    
    // Hot key ID; can be any value in the range 0x0000 through 0xBFFF
    // Allows the application to identify this hot key in case there is more than one
    constexpr int MyHotKeyId = 42;
    
    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) {
        // ...
    
        // Register a global system-wide hot key (Ctrl+Shift+Alt+I)
        if (!::RegisterHotKey(this->m_hWnd, MyHotKeyId,
                              MOD_ALT | MOD_SHIFT | MOD_CONTROL | MOD_NOREPEAT,
                              'I')) {
            auto const ec = ::GetLastError();
            auto const err_msg = std::format(L"RegisterHotKey failed (error: {})\n",
                                             ec);
            ::OutputDebugStringW(err_msg.c_str());
    
            // Probably a good idea to handle failure more gracefully than this:
            return -1;
        }
    
        return 0;
    }
    
    void CMainFrame::OnHotKey(UINT nHotKeyId, UINT nKey1, UINT nKey2) {
        // Handle hot key; nHotKeyId is MyHotKeyId in this case
    }
    

    Note that receiving a WM_HOTKEY message grants the receiving thread foreground activation permission. If you want your application to come to the foreground upon receiving a registered hot key, you can use the following OnHotKey implementation:

    void CMainFrame::OnHotKey(UINT nHotKeyId, UINT nKey1, UINT nKey2) {
        if (this->IsIconic()) {
            this->ShowWindow(SW_RESTORE);
        }
        this->SetForegroundWindow();
    }
    

    No flashing the taskbar button, everything just works as intended.