原文链接: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对象,然后进行事件处理。

之前博主也写过几篇关于全局事件过滤的文章,有兴趣的同学可以去了解下

  1. Qt之nativeEventFilter和notify
  2. qt捕获全局windows消息
  3. Qt之模拟窗口失去焦点隐藏
  4. Qt之移动硬盘热插拔监控
  5. Qt之自定义托盘
  6. Qt之自定义检索框
  7. Qt之自定义托盘(二)
  8. 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重键盘事件监听方式,但是只有第三种方式才可以满足我们当前的需求

四、相关文章

  1. Qt之nativeEventFilter和notify
  2. qt捕获全局windows消息
  3. Qt之模拟窗口失去焦点隐藏
  4. Qt之移动硬盘热插拔监控
  5. Qt之自定义托盘
  6. Qt之自定义检索框
  7. Qt之自定义托盘(二)
  8. Qt之股票组件-股票检索--支持搜索结果预览、鼠标、键盘操作
  9. Qt获取Capslock键(大小写键)状态
  10. Qt判断大小写键Caps Lock状态

值得一看的优秀文章:

  1. 财联社-产品展示
  2. 广联达-产品展示
  3. Qt定制控件列表
  4. 牛逼哄哄的Qt库
如果您觉得文章不错,不妨给个打赏,写作不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!!

很重要--转载声明

  1. 本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者:朝十晚八 or Twowords

  2. 如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。


Qt之键盘事件监听-实时响应大小写Capslock按键的更多相关文章

  1. java鼠标与键盘事件监听

    package cn.stat.p3.windowdemo; import java.awt.Button; import java.awt.FlowLayout; import java.awt.F ...

  2. 关于v4包的Fragment过渡动画的事件监听无响应问题解决

    项目中部分功能模块采用了单Activity+多Fragment模式,当Fragment切换时,需要在过渡动画执行完后做一些操作,通常就是在自己封装的FragmentBase中重写onCreateAni ...

  3. egret键盘事件监听

    document.addEventListener("keydown", function (event: any) { //alert(event.key); //console ...

  4. JS键盘事件监听

    window.onload=function(){ var keyword = document.getElementById("keyword"); keyword.onkeyu ...

  5. java 事件监听 - 键盘

    java 事件监听 - 键盘 //事件监听 //键盘事件监听,写了一个小案例,按上下左右,改变圆形的位置,圆形可以移动 import java.awt.*; import javax.swing.*; ...

  6. 关于android软键盘enter键的替换与事件监听

    android软键盘事件监听enter键  软件盘的界面替换只有一个属性android:imeOptions,这个属性的可以取的值有 normal,actionUnspecified,actionNo ...

  7. jQuery EasyUI/TopJUI输入框事件监听

    jQuery EasyUI/TopJUI输入框事件监听 代码如下: <div data-toggle="topjui-panel" title="" da ...

  8. Android软键盘的隐藏显示、事件监听的代码

    把开发过程中重要的一些内容片段做个珍藏,如下资料是关于Android软键盘的隐藏显示.事件监听的内容,应该是对小伙伴们有所用途. public class ResizeLayout extends L ...

  9. vue样式绑定、事件监听、表单输入绑定、响应接口

    1.样式绑定 操作元素的 class 列表和内联样式是数据绑定的一个常见需求.因为它们都是属性,所以我们可以用 v-bind 处理它们:只需要通过表达式计算出字符串结果即可.不过,字符串拼接麻烦且易错 ...

随机推荐

  1. Starling 环形进度条实现

    项目初期想实现这个效果来着,查了很多资料(包括式神的<神奇的滤镜>),也没找到完美的实现方法,,当时时间紧迫,就找了传统的进度条来代替实现. 最近偶然心血来潮,查了各方面资料,终于找到实现 ...

  2. GD32电压不足时烧写程序导致程序运行异常的解决方法

    一直使用的GD32F450前段时间遇到这样一个问题,当使用J-Link供电给板子烧写程序之后,程序运行缓慢,就像运行在FLASH高速部分之外一样,但是如果使用外部供电烧写,就不会出现这个问题,而且一旦 ...

  3. JS扫雷小游戏

    HTML代码 <title> 扫雷 </title> <!-- ondragstart:防拖拽生成新页面 oncontextmenu:屏蔽右键菜单--> <b ...

  4. 状压DP概念 及例题(洛谷 P1896 互不侵犯)

    状压DP 就是状态压缩DP.所谓状态压缩,就是将一些复杂的状态压缩起来,一般来说是压缩为一个二进制数,用01来表示某一元素的状态. 比如一排灯泡(5个) 我们可以用一串二进制01串来表示他们的状态 1 ...

  5. Kubernetes容器云平台建设实践

    [51CTO.com原创稿件]Kubernetes是Google开源的一个容器编排引擎,它支持自动化部署.大规模可伸缩.应用容器化管理.伴随着云原生技术的迅速崛起,如今Kubernetes 事实上已经 ...

  6. Yii GridView Ajax 刷新

    Yii GridView  Ajax 刷新,当页面点击一个按钮时,刷新数据. 1.控制器 <?php class privController extends Controller{ publi ...

  7. NN入门,手把手教你用Numpy手撕NN(一)

    前言 这是一篇包含极少数学推导的NN入门文章 大概从今年4月份起就想着学一学NN,但是无奈平时时间不多,而且空闲时间都拿去做比赛或是看动漫去了,所以一拖再拖,直到这8月份才正式开始NN的学习. 这篇文 ...

  8. application.yml 增加数据库连接,重启日志卡死

    SpringBoot引入JPA,application.ymlapplication.yml增加数据库链接参数,启动卡死,日志没有动,如下图 折腾好久,后面发现用 Maven的package 过程中 ...

  9. c++json构建与解析组件 RapidJSON 没用过永远不会知道有多好用

    参考资料: 官方文档 推荐[腾讯RapidJSON]学习笔记 原理请参考以上资料 构建json Document doc; Document::AllocatorType &allocator ...

  10. unity之加载场景

    游戏中的Loading分为:静态Loading和动态Loading. 简单形象的做个比喻: 静态Loading可能就是一张背景图.而动态的Loading就是在读取的同时有一个东西在“转圈”. 1.静态 ...