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對象,然后進行事件處理。

之前博主也寫過幾篇關於全局事件過濾的文章,有興趣的同學可以去了解下

  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. 如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM