前言
Qt 提供了QShortcut来实现快捷键的功能,不过它的响应范围只局限于窗口内。如果脱离了进程窗口,如在桌面,或者正在使用其他软件,是无法响应的。
要解决这样的问题可以使用Windows 提供的API再结合qt的一些功能来实现一个全局可响应的快捷键。
需要用到的技术
RegisterHotKey()
这是是一个winAPI函数,它的作用是像系统注册一个快捷键,注册成功后系统会根据全局的键盘输入来判断快捷键是否被触发,触发之后通过WinMessage的方式来通知注册者。
BOOL WINAPI RegisterHotKey( HWND hWnd, int id, UINT fsModifiers, UINT vk );
这是它的函数原型
参数介绍
hWnd:接收热键产生WM_HOTKEY消息的窗口句柄。若该参数NULL,传递给调用线程的WM_HOTKEY消息必须在消息循环中进行处理。
id:定义热键的标识符。调用线程中的其他热键,不能使用同样的标识符。应用程序必须定义一个0X0000-0xBFFF范围的值。一个共享的动态链接库(DLL)必须定义一个范围为0xC000-0xFFFF的值(GlobalAddAtomA函数返回该范围)。为了避免与其他动态链接库定义的热键冲突,一个DLL必须使用GlobalAddAtomA函数获得热键的标识符。
fsModifoers:定义为了产生WM_HOTKEY消息而必须与由nVirtKey参数定义的键一起按下的键。
该参数可以是如下值的组合:
键 | 值 | 含意 |
---|---|---|
MOD_ALT | 0x0001 | 按下的可以是任一Alt键。 |
MOD_SHIFT | 0x0004 | 按下的可以是任一Shift键。 |
MOD_WIN | 0x0008 | 按下的可以是任一Windows徽标键。 |
MOD_NOREPEAT | 0x4000 | 更改热键行为,以便键盘自动重复不会产生多个热键通知。 |
MOD_CONTROL | 0x0002 | 按下的可以是任一Ctrl键。 |
vk: 定义热键的虚拟键码。
QT事件机制处理windows原生事件
QApplication::installNativeEventFilter() 是QT提供的一种事件机制,可以通过这个函数安装一个过滤器来处理Windows原生的事件,这个可以参考之前的博客 QT事件机制的简单使用。
使用时,需要继承QAbstractNativeEventFilter类,实现它的纯虚函数:
virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) = 0
在这个函数里处理Windows原生的事件消息。
message指针可以强制转换为WinApi中的Msg类型,然后按照Msg中的信息处理即可。
最后调用QApplication::installNativeEventFilter() 传入类对象指针即可。
实现步骤
- 调用RegisterHotKey()注册一个系统级的快捷键
- 实现一个事件过滤器
- 将之间过滤器安装到QApplication
具体代码
.h
#pragma once #include <QAbstractNativeEventFilter> class NativeEventFilter :public QAbstractNativeEventFilter { public: NativeEventFilter(const unsigned int& mod, const unsigned int& key); public: bool nativeEventFilter(const QByteArray& eventType, void* message, long* result); unsigned int mod, key; }; class ShortcutTest{ public: ShortcutTest() {}; ~ShortcutTest() {}; public: void registerShortcut(const NativeEventFilter &filter); };
.cpp
#include "ShortcutTest.h" #include <windows.h> #include <iostream> NativeEventFilter::NativeEventFilter(const unsigned int& mod, const unsigned int& key) { this->mod = mod; this->key = key; } bool NativeEventFilter::nativeEventFilter(const QByteArray& eventType, void* message, long* result) { MSG* msg = static_cast<MSG*>(message); if (msg->message == WM_HOTKEY) { const quint32 keycode = HIWORD(msg->lParam); const quint32 modifiers = LOWORD(msg->lParam); if (keycode == key && mod == modifiers) std::cerr << "shortcut trigger!" << std::endl; } return false; } void ShortcutTest::registerShortcut(const NativeEventFilter& filter) { int id = filter.key ^ filter.mod; BOOL ok = RegisterHotKey(0, id, filter.mod, filter.key); if (!ok) std::cerr << "register shortcut failed!" << std::endl; }
main()
int main(int argc, char *argv[]) { QApplication a(argc, argv); NativeEventFilter filter(MOD_ALT,'W'); a.installNativeEventFilter(&filter); ShortcutTest test; test.registerShortcut(filter); return a.exec(); }
运行结果:
补充
在代码中传虚拟按键代码时,传了一个char ‘w’ 过去,最开始我是以为字母区的虚拟码应该是VK_xx这样的格式,尝试了以下并不是。然后就尝试到头文件中去找,找到了这样一段注释:
/* * VK_0 - VK_9 are the same as ASCII '0' - '9' (0x30 - 0x39) * 0x40 : unassigned * VK_A - VK_Z are the same as ASCII 'A' - 'Z' (0x41 - 0x5A) */
因为数字键和字母键的键码和他们的ASCII码是一样的,所以在头文件中没有定义。
注意:这里的字母要使用大写的,小写的不行
Qt::key 转换为WinAPi的虚拟键码的互相转换
QT 中提供QKeySequenceEdit 给用户输入快捷键,得到是结果是Qt::key类型的,想要使用WinAPI注册快捷键还需要一些转换,这里提供以下转换函数,否则又是一个体力活。
unsigned int nativeModifiers(Qt::KeyboardModifiers modifiers) { quint32 native = 0; if (modifiers & Qt::ShiftModifier) native |= MOD_SHIFT; if (modifiers & Qt::ControlModifier) native |= MOD_CONTROL; if (modifiers & Qt::AltModifier) native |= MOD_ALT; if (modifiers & Qt::MetaModifier) native |= MOD_WIN; return native; } unsigned int nativeKeycode(Qt::Key key) { switch (key) { case Qt::Key_Escape: return VK_ESCAPE; case Qt::Key_Tab: case Qt::Key_Backtab: return VK_TAB; case Qt::Key_Backspace: return VK_BACK; case Qt::Key_Return: case Qt::Key_Enter: return VK_RETURN; case Qt::Key_Insert: return VK_INSERT; case Qt::Key_Delete: return VK_DELETE; case Qt::Key_Pause: return VK_PAUSE; case Qt::Key_Print: return VK_PRINT; case Qt::Key_Clear: return VK_CLEAR; case Qt::Key_Home: return VK_HOME; case Qt::Key_End: return VK_END; case Qt::Key_Left: return VK_LEFT; case Qt::Key_Up: return VK_UP; case Qt::Key_Right: return VK_RIGHT; case Qt::Key_Down: return VK_DOWN; case Qt::Key_PageUp: return VK_PRIOR; case Qt::Key_PageDown: return VK_NEXT; case Qt::Key_F1: return VK_F1; case Qt::Key_F2: return VK_F2; case Qt::Key_F3: return VK_F3; case Qt::Key_F4: return VK_F4; case Qt::Key_F5: return VK_F5; case Qt::Key_F6: return VK_F6; case Qt::Key_F7: return VK_F7; case Qt::Key_F8: return VK_F8; case Qt::Key_F9: return VK_F9; case Qt::Key_F10: return VK_F10; case Qt::Key_F11: return VK_F11; case Qt::Key_F12: return VK_F12; case Qt::Key_F13: return VK_F13; case Qt::Key_F14: return VK_F14; case Qt::Key_F15: return VK_F15; case Qt::Key_F16: return VK_F16; case Qt::Key_F17: return VK_F17; case Qt::Key_F18: return VK_F18; case Qt::Key_F19: return VK_F19; case Qt::Key_F20: return VK_F20; case Qt::Key_F21: return VK_F21; case Qt::Key_F22: return VK_F22; case Qt::Key_F23: return VK_F23; case Qt::Key_F24: return VK_F24; case Qt::Key_Space: return VK_SPACE; case Qt::Key_Asterisk: return VK_MULTIPLY; case Qt::Key_Plus: return VK_ADD; case Qt::Key_Comma: return VK_SEPARATOR; case Qt::Key_Minus: return VK_SUBTRACT; case Qt::Key_Slash: return VK_DIVIDE; case Qt::Key_MediaNext: return VK_MEDIA_NEXT_TRACK; case Qt::Key_MediaPrevious: return VK_MEDIA_PREV_TRACK; case Qt::Key_MediaPlay: return VK_MEDIA_PLAY_PAUSE; case Qt::Key_MediaStop: return VK_MEDIA_STOP; case Qt::Key_VolumeDown: return VK_VOLUME_DOWN; case Qt::Key_VolumeUp: return VK_VOLUME_UP; case Qt::Key_VolumeMute: return VK_VOLUME_MUTE; case Qt::Key_0: case Qt::Key_1: case Qt::Key_2: case Qt::Key_3: case Qt::Key_4: case Qt::Key_5: case Qt::Key_6: case Qt::Key_7: case Qt::Key_8: case Qt::Key_9: return key; case Qt::Key_A: case Qt::Key_B: case Qt::Key_C: case Qt::Key_D: case Qt::Key_E: case Qt::Key_F: case Qt::Key_G: case Qt::Key_H: case Qt::Key_I: case Qt::Key_J: case Qt::Key_K: case Qt::Key_L: case Qt::Key_M: case Qt::Key_N: case Qt::Key_O: case Qt::Key_P: case Qt::Key_Q: case Qt::Key_R: case Qt::Key_S: case Qt::Key_T: case Qt::Key_U: case Qt::Key_V: case Qt::Key_W: case Qt::Key_X: case Qt::Key_Y: case Qt::Key_Z: return key; default: return 0; } }
文章评论
哈哈 搞定了
大佬,为啥我抄你的大佬运行不了啊。。 加个QQ 解答下呗2602118005