Qt之鍵盤事件監聽-實時響應大小寫Capslock按鍵
目錄
- 一、開篇
- 二、效果展示
- 三、實現思路
- 1、重寫QLlinEdit
- 2、全域性應用程式事件
- 3、windows鉤子
- 四、相關文章
原文連結: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捕獲全域性windows訊息
- 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捕獲全域性windows訊息
- Qt之模擬視窗失去焦點隱藏
- Qt之行動硬碟熱插拔監控
- Qt之自定義托盤
- Qt之自定義檢索框
- Qt之自定義托盤(二)
- Qt之股票元件-股票檢索--支援搜尋結果預覽、滑鼠、鍵盤操作
- Qt獲取Capslock鍵(大小寫鍵)狀態
- Qt判斷大小寫鍵Caps Lock狀態
值得一看的優秀文章:
- 財聯社-產品展示
- 廣聯達-產品展示
- Qt定製控制元件列表
- 牛逼哄哄的Qt庫
如果您覺得文章不錯,不妨給個打賞,寫作不易,感謝各位的支援。您的支援是我最大的動力,謝謝!!!
很重要--轉載宣告
本站文章無特別說明,皆為原創,版權所有,轉載時請用連結的方式,給出原文出處。同時寫上原作者:朝十晚八 or Twowords
如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。