Qt之键盘事件监听-实时响应大小写Capslock按键

原文连接:Qt之键盘事件监听-实时响应大小写Capslock按键windows

1、开篇

假期老是转眼即逝,想一想今天就是中秋节最后一天了,明天又要开始挤地铁了,好像还有一篇文章须要完成,前一段时间作了一个小功能,当用户输入密码时,若是键盘开启了大写,则须要重点提示用户,不然有些用户可能会误觉得本身密码输入错误。ide

今天博主就来分析下当时的实现过程。函数

本篇文章主要讲解怎么实现实时监听大小写的过程,其余内容不作详细说明。文章分析的主线路是按博主当时完成此项功能的一个思路,虽然最后的解决方案才是对的,但前边一些尝试性的解决方案,博主这里仍是都写了下来。一方面能够避免你们再去作无用的尝试,另外一方面也是对本身实现这一功能时的一个总结。post

2、效果展现

按照惯例先上图,看看是否是同窗们想一想中的效果。测试

3、实现思路

如下分几个小结来分析博主当时实现大小写监听的一个思路,虽然前两种方式不能达到最后的需求,可是你们也能够看看,或许他更适合于你另外一种需求下的场景呢!优化

在讲各类实现方案时,咱们先来搞清楚怎么获取当前键盘是否开启了大写,方法比较简单,只修要经过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

一、重写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接口,当发现是大小写按键事件时,咱们优先响应下,可是绝对不中断事件循环,这样不就完成咱们的工了嘛!

二、全局应用程序事件

要获取全局应用程序事件,前边提到了重写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的钩子。

三、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重键盘事件监听方式,可是只有第三种方式才能够知足咱们当前的需求

4、相关文章

  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. 如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时经过修改本文达到有利于转载者的目的。

相关文章
相关标签/搜索