原文连接:Qt之键盘事件监听-实时响应大小写Capslock按键windows
假期老是转眼即逝,想一想今天就是中秋节最后一天了,明天又要开始挤地铁了,好像还有一篇文章须要完成,前一段时间作了一个小功能,当用户输入密码时,若是键盘开启了大写,则须要重点提示用户,不然有些用户可能会误觉得本身密码输入错误。ide
今天博主就来分析下当时的实现过程。函数
本篇文章主要讲解怎么实现实时监听大小写的过程,其余内容不作详细说明。文章分析的主线路是按博主当时完成此项功能的一个思路,虽然最后的解决方案才是对的,但前边一些尝试性的解决方案,博主这里仍是都写了下来。一方面能够避免你们再去作无用的尝试,另外一方面也是对本身实现这一功能时的一个总结。post
按照惯例先上图,看看是否是同窗们想一想中的效果。测试
如下分几个小结来分析博主当时实现大小写监听的一个思路,虽然前两种方式不能达到最后的需求,可是你们也能够看看,或许他更适合于你另外一种需求下的场景呢!优化
在讲各类实现方案时,咱们先来搞清楚怎么获取当前键盘是否开启了大写,方法比较简单,只修要经过LOBYTE(GetKeyState(VK_CAPITAL))
函数便可获取。this
最终咱们的键盘相应函数可能会像下面这样,当发现了键盘按下(抬起)事件时,咱们就调用这个函数从新设置大写提示spa
void CPasswordEdit::UpdateCapslockTip() { if (LOBYTE(GetKeyState(VK_CAPITAL)) == false) { m_ActCaps->setIcon(QIcon(":/PasswordWidget/64.png")); } else { m_ActCaps->setIcon(QIcon()); } }
知道了如何判断是否开启键盘大写后,下一步就是须要搞清楚这个函数的触发时机,下面是博主的各类尝试过程。.net
要监听键盘事件,博主第一时间想到的就是继承这个控件,重写该控件的键盘回调函数,当该回调函数被触发时,就是有键盘按键被按下。
virtual void keyPressEvent(QKeyEvent * event) override; virtual void keyReleaseEvent(QKeyEvent * event) override;
以上两个函数就是咱们须要重写的两个按钮回调函数,函数的实现比较简单,判断当前是不是大小写按钮事件,若是有就执行UpdateCapslockTip函数,更新当前给用户的提示。
void CPasswordEdit::keyPressEvent(QKeyEvent * event) { if (event->key() == Qt::Key_CapsLock) { UpdateCapslockTip(); } QLineEdit::keyPressEvent(event); } void CPasswordEdit::keyReleaseEvent(QKeyEvent * event) { if (event->key() == Qt::Key_CapsLock) { UpdateCapslockTip(); } QLineEdit::keyReleaseEvent(event); }
实现起来是否是还挺简单的。进行一下简单测试,当编辑框获取焦点时,咱们按下大小写按键,程序能够正常的执行啦。
若是多测试测试,你可能就会发现,当编辑框没有焦点时,也就是说焦点在咱们的程序的其余控件上时,这个两个函数就进不来了。
为何会出现这个状况呢,对Qt的事件循环稍微熟悉的同窗应该都会比较清楚,由于其余有焦点的控件有优先处理该键盘事件,而且人家也把事件处理了,那么Qt的事件循环就会被中断掉,咱们的控件天然就收不到消息了。
为了解决这个问题,博主想到了另一种方法,那就是继承QApplication类,重写notify接口,当发现是大小写按键事件时,咱们优先响应下,可是绝对不中断事件循环,这样不就完成咱们的工了嘛!
要获取全局应用程序事件,前边提到了重写QApplication类的notify接口,还有另一种更加轻量的方式,那就是经过installNativeEventFilter
接口安装全局事件过滤器。
想要过滤全局事件,首先咱们的类须要继承自QAbstractNativeEventFilter这个类,像下面声明代码这样。
class CPasswordEdit : public QLineEdit, public QAbstractNativeEventFilter { ... virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override; ... };
事件过滤函数nativeEventFilter函数的第二个参数在windows下就能够转换为MSG对象,而后进行事件处理。
以前博主也写过几篇关于全局事件过滤的文章,有兴趣的同窗能够去了解下
bool CPasswordEdit::nativeEventFilter( const QByteArray &eventType, void *message, long *result ) { if ("windows_dispatcher_MSG" == eventType || "windows_generic_MSG" == eventType) { MSG * msg = reinterpret_cast<MSG *>(message); if(msg->message == WM_DEVICECHANGE) { case WM_KEYUP: case WM_KEYDOWN: if (((KBDLLHOOKSTRUCT *)lParam)->vkCode == 20) { UpdateCapslockTip(); } break; } } return __super::nativeEvent(eventType, message, result); }
过滤了全局事件循环后,不管在咱们的程序哪一个地方按下键盘按键,咱们的编辑框均可以获取事件,这下好像没有问题了。
若是再多测试测试,你可能就会发现,当咱们的程序没有焦点时,也就是说焦点在其余应用程序上时,过滤本App的事件循环也很差使。
思来想去,若是一直纠结于本程序的事件处理好像这个功能很难完成,最后仍是得借助于windows的钩子。
windwos钩子是windwos系统提供给咱们的一个很方便的函数,咱们可使用钩子把咱们的函数挂载在windows系统的事件处理流程中,具体挂载在哪一个位置,系统已经帮咱们想好了,咱们就不用操心了,重点是咱们须要明白,咱们能够处理全局事件。
这样windows这样的设计是把全部人调用该接口的人都当作是一个好人了,假设说有一个App首先拿到了事件处理权,若是他执行完事件处理函数后没有把钩子交还给下一我的处理,那么本次事件循环也就到此结束,其余钩子、或者本应该处理消息的程序也就收不到该事件。
因此使用钩子时,有一个规范,那就是咱们调用完钩子处理函数后,须要调用CallNextHookEx函数让事件循环继续下去。
有了以上简单说明,也用到了windows钩子,那么咱们的程序实现功能确定没啥问题。
下面就是博主为了更优化的实现钩子而声明的一个类。该类的构造函数中咱们把回调函数帮到系统事件循环中,当类析构时,再把钩子析构掉。
class LowLevelKeyboardHook { public: LowLevelKeyboardHook(); ~LowLevelKeyboardHook(); public: static LRESULT CALLBACK keyHookEvent(int nCode, WPARAM wParam, LPARAM lParam); void SetKeyboardCall(const std::function<void ()> & func){ m_func = func; } private: static HHOOK keyborard_hook_; static std::function<void()> m_func; };
钩子的使用上必定要当心,由于钩子属于系统级的事件处理,若是发生了错误则会影响其余应用程序的执行,因此钩子的使用范围咱们也应该尽量的小。
LowLevelKeyboardHook::LowLevelKeyboardHook() { Q_ASSERT(!keyborard_hook_); keyborard_hook_ = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)keyHookEvent, GetModuleHandle(NULL), 0); } LowLevelKeyboardHook::~LowLevelKeyboardHook() { if (nullptr != keyborard_hook_) { UnhookWindowsHookEx(keyborard_hook_); keyborard_hook_ = nullptr; } }
有了完美的绑定回调函数的方式,下面来看看回到函数的处理流程>
LRESULT CALLBACK LowLevelKeyboardHook::keyHookEvent(int code, WPARAM wParam, LPARAM lParam) { if (code < 0) return CallNextHookEx(keyborard_hook_, code, wParam, lParam); if (wParam == WM_KEYDOWN) { //用户按下了Capslock键 //Capslock对应键码为20 if (((KBDLLHOOKSTRUCT *)lParam)->vkCode == 20) { if (m_func) { m_func(); } } } return CallNextHookEx(keyborard_hook_, code, wParam, lParam); }
当有大小写按键触发时,执行了名为m_func
的回调函数。该回调函数就是咱们构造LowLevelKeyboardHook对象时注册进来的函数,当钩子的回调函数执行m_func()
函数时,就至关于执行了被注册进来的回调函数。
以下代码是构造了一个钩子辅助类LowLevelKeyboardHook对象,并把CPasswordEdit类的UpdateCapslockTip函数绑定给了钩子,当执行m_func()
函数时,就至关于执行了UpdateCapslockTip函数。
static LowLevelKeyboardHook keyboard; keyboard.SetKeyboardCall(std::bind(&CPasswordEdit::UpdateCapslockTip, this));
UpdateCapslockTip函数第三小节开始的时候已经说过,这里就不在说明。
到这里本篇文章全部内容基本讲述完毕,总共有3重键盘事件监听方式,可是只有第三种方式才能够知足咱们当前的需求
值得一看的优秀文章:
![]() |
![]() |
很重要--转载声明