Qt之键盘事件监听-实时响应大小写Capslock按键
原文链接:qt之键盘事件监听-实时响应大小写capslock按键
一、开篇
假期总是转眼即逝,想想今天就是中秋节最后一天了,明天又要开始挤地铁了,好像还有一篇文章需要完成,前一段时间做了一个小功能,当用户输入密码时,如果键盘开启了大写,则需要重点提示用户,否则有些用户可能会误以为自己密码输入错误。
今天博主就来分析下当时的实现过程。
本篇文章主要讲解怎么实现实时监听大小写的过程,其他内容不做详细说明。文章分析的主线路是按博主当时完成此项功能的一个思路,虽然最后的解决方案才是对的,但前边一些尝试性的解决方案,博主这里还是都写了下来。一方面可以避免大家再去做无用的尝试,另一方面也是对自己实现这一功能时的一个总结。
二、效果展示
按照惯例先上图,看看是不是同学们想想中的效果。
三、实现思路
以下分几个小结来分析博主当时实现大小写监听的一个思路,虽然前两种方式不能达到最后的需求,但是大家也可以看看,或许他更适合于你另一种需求下的场景呢!
在讲各种实现方案时,我们先来搞清楚怎么获取当前键盘是否开启了大写,方法比较简单,只修要通过lobyte(getkeystate(vk_capital))
函数即可获取。
最终我们的键盘相应函数可能会像下面这样,当发现了键盘按下(抬起)事件时,我们就调用这个函数重新设置大写提示
void cpasswordedit::updatecapslocktip() { if (lobyte(getkeystate(vk_capital)) == false) { m_actcaps->seticon(qicon(":/passwordwidget/64.png")); } else { m_actcaps->seticon(qicon()); } }
知道了如何判断是否开启键盘大写后,下一步就是需要搞清楚这个函数的触发时机,下面是博主的各种尝试过程。
1、重写qllinedit
要监听键盘事件,博主第一时间想到的就是继承这个控件,重写该控件的键盘回调函数,当该回调函数被触发时,就是有键盘按键被按下。
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接口,当发现是大小写按键事件时,我们优先响应下,但是绝对不中断事件循环,这样不就完成我们的工了嘛!
2、全局应用程序事件
要获取全局应用程序事件,前边提到了重写qapplication类的notify接口,还有另外一种更加轻量的方式,那就是通过installnativeeventfilter
接口安装全局事件过滤器。
想要过滤全局事件,首先我们的类需要继承自qabstractnativeeventfilter这个类,像下面声明代码这样。
class cpasswordedit : public qlineedit, public qabstractnativeeventfilter { ... virtual bool nativeeventfilter(const qbytearray &eventtype, void *message, long *result) override; ... };
事件过滤函数nativeeventfilter函数的第二个参数在windows下就可以转换为msg对象,然后进行事件处理。
之前博主也写过几篇关于全局事件过滤的文章,有兴趣的同学可以去了解下
- qt之nativeeventfilter和notify
- qt之模拟窗口失去焦点隐藏
- qt之移动硬盘热插拔监控
- qt之自定义托盘
- qt之自定义检索框
- qt之自定义托盘(二)
- qt之股票组件-股票检索--支持搜索结果预览、鼠标、键盘操作
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的钩子。
3、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重键盘事件监听方式,但是只有第三种方式才可以满足我们当前的需求
四、相关文章
- qt之nativeeventfilter和notify
- qt之模拟窗口失去焦点隐藏
- qt之移动硬盘热插拔监控
- qt之自定义托盘
- qt之自定义检索框
- qt之自定义托盘(二)
- qt之股票组件-股票检索--支持搜索结果预览、鼠标、键盘操作
- qt获取capslock键(大小写键)状态
- qt判断大小写键caps lock状态
值得一看的优秀文章:
如果您觉得文章不错,不妨给个打赏,写作不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!!
很重要--转载声明
本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者: or twowords
如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。
下一篇: 如何避免死锁?我们有套路可循