原文連接: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重鍵盤事件監聽方式,可是隻有第三種方式才能夠知足咱們當前的需求
值得一看的優秀文章:
很重要--轉載聲明