轉自:https://blog.csdn.net/qq_27450255/article/details/78780959
由於項目需求,需要實現基於鍵盤按鍵的復用,查了很多資料都不滿足我的需求,其中Mango的吐槽一下Qt的按鍵消息響應對我啟發很大,他闡述了關於按鍵長按的問題,我的測試結果和他有些出入,但總體思路是一樣的,也歡迎大家指正。下面來說一下具體的實現過程。
鍵盤按鍵單擊、雙擊
首先鍵盤按鍵的單擊、雙擊實現,沒錯!就是用的QTimer,一說到這估計大部分人都知道怎么回事了,但這里也有個誤區,那就是如何區分單擊和雙擊的問題,這也是我實現過程中遇到的問題。我最開始的做法是根據按下和釋放的時間間隔來區分的,現在看來這顯然是不對的,但當時腦袋可能蒙圈了(>_<),這樣是無法准確的區分單擊和雙擊的,應該用兩次按鍵的時間間隔來區分,這里在按下、或釋放里實現都是可以的,我最后選擇在釋放里實現,后面再說原因。如果誰不清楚按下、釋放什么意思,自己去查qt幫助文檔吧。。。
頭文件里定義幾個相關變量
1 //MyClass.h 2 QTimer* timer_; 3 bool LongPress_;//后面用到 4 int ClickCount_;//按鍵計數 5 KeyFlag KeyFlag_;//枚舉
1 enum KeyFlag 2 { 3 kKey_NULL, 4 kKey_A, 5 kKey_S, 6 kKey_D, 7 kKey_F, 8 kKey_J, 9 kKey_K 10 };
構造函數連接timeout信號到單擊函數
1 //MyClass.cpp 2 timer_ = new QTimer(this); 3 connect(timer_, SIGNAL(timeout()), this, SLOT(KeyOneClick())); 4 ClickCount_ = 0; 5 LongPress_ = false; 6 KeyFlag_ = kKey_NULL;
重寫void keyReleaseEvent(QKeyEvent *event)
1 void MyClass::keyReleaseEvent(QKeyEvent *event) { 2 switch (event->key()) 3 { 4 case Qt::Key_A: 5 if (!timer_->isActive()) {//計數期間,如果QTimer已開始,則不重新開始 6 timer_->start(400);//400ms是判斷雙擊的時間間隔,不唯一,可根據實際情況改變 7 KeyFlag_ = kKey_A;//單擊函數的按鍵標識 8 } 9 ClickCount_++;//點擊計數,在400ms內如果點擊兩次認為是雙擊 10 if (ClickCount_ == 2){ 11 timer_->stop();//停止計時 12 ClickCount_ = 0;//計數清零 13 cout << "this is A double click" << endl; 14 DoDoubleThings();//執行按鍵A雙擊動作 15 } 16 break; 17 18 case Qt::Key_S://注意到沒?我實現的都是字母鍵,其他鍵是不一樣的 19 break; 20 case Qt::Key_D: 21 break; 22 case Qt::Key_F: 23 break; 24 case Qt::Key_J: 25 break; 26 case Qt::Key_K: 27 break; 28 default: 29 break; 30 } 31 32 }
單擊函數,在400ms內未達到雙擊次數,也就是未執行timer_->stop();時間耗盡觸發timeout信號,執行單擊動作。這里提一下stop()函數,QTimer執行start(n)后,如果不stop(),那它會循環執行。
void MyClass::KeyOneClick() { switch (KeyFlag_)//判斷點擊的是哪個按鍵 { case kKey_A: ClickCount_ = 0;//計數清零 timer_->stop();//停止計時 cout << "this is A ckick" << endl; DoSigalClickThings();//單擊A的動作 break; case kKey_S: break; case kKey_D: break; case kKey_F: break; case kKey_J: break; case kKey_K: break; default: break; } }
鍵盤按鍵長按
至此實現鍵盤單擊和雙擊復用,那么我們再來看一下長按怎么處理呢?
先看一下按鍵長按的過程分析,我們知道一按一松實現一次click動作,那我們測試一下qt鍵盤長按的具體過程,重寫void keyPressEvent(QKeyEvent *event)、void keyReleaseEvent(QKeyEvent *event)函數。
為了區分是否是長按,QKeyEvent 提供了一個isAutoRepeat()函數自動檢測按鍵是否長按
- 長按返回true
- 非長按返回false
為了方便表示我定義
- P:press動作
- R:release動作
- T:isAutoRepeat()返回true
- F:isAutoRepeat()返回false
下面看一下長按會發生什么吧。
1 void MyClass::keyPressEvent(QKeyEvent *event) { 2 switch (event->key()) 3 { 4 case Qt::Key_A: 5 if (!event->isAutoRepeat()) { 6 //非長按輸出press、not repeat等關鍵詞 7 cout << "this is A press: not Auto Repeat" << endl; 8 } 9 else{ 10 //長按輸出press、is repeat等關鍵詞 11 cout << "this is A press: is Auto Repeat" << endl; 12 } 13 break; 14 default: 15 break; 16 } 17 18 } 19 20 void MyClass::keyReleaseEvent(QKeyEvent *event) { 21 switch (event->key()) 22 { 23 case Qt::Key_A: 24 if (!event->isAutoRepeat()) { 25 //非長按輸出release、not repeat等關鍵詞 26 cout << "this is A release: not Auto Repeat" << endl; 27 } 28 else{ 29 //非長按輸出release、is repeat等關鍵詞 30 cout << "this is A release: is Auto Repeat" << endl; 31 } 32 break; 33 default: 34 break; 35 } 36 37 }
運行結果用我的方式表示為:P(F)R(T)P(T)R(T)…P(T)R(T)P(T)R(F),也就是當你長按時會循環發生press和release動作,
- 第一次執行press動作,此時QKeyEvent 不認為你在長按,而在release時,QKeyEvent 已經開始認為你在長按了;
- 第二次到倒數第二次QKeyEvent 認為你都在長按;
- 最后一次,press動作依然為長按,但release卻變成非長按了;
也就是不管你按多久最開始的press肯定為非長按狀態,最后的release肯定為非長按狀態。結合這些特性,我們來實現鍵盤按鍵的復用,即同時實現單擊雙擊和長按三個動作。
前面提到單擊和雙擊的區分,其實在void keyPressEvent(QKeyEvent *event)、void keyReleaseEvent(QKeyEvent *event)函數里都可以,反正都是記錄時間差,press-press或release-release沒分別,那最后為什么選擇在keyReleaseEvent(QKeyEvent *event)函數里實現呢?
問題就在還得同時實現長按功能,剛剛分析得出無論你長按還是非長按,第一次的press動作他都是P(F)的,如果在void keyPressEvent(QKeyEvent *event)里實現,那長按必然會附加一次單擊,這當然不是我們想要的;
再來看看在void keyReleaseEvent(QKeyEvent *event),如果長按,它第一次就是R(T)了,那就可以通過判斷isAutoRepeat()的狀態來區分長按和非長按了。
還有一個問題就是,雖然可以判斷長按了,但是長按時是會循環執行的,如不控制,豈不會執行n次長按要實現的動作,因此還要加一個flag來控制,讓它只執行一次。
最后,還要討論一下長按的最后一次release動作,它和非長按的release是相同的R(F),為了避免這種情況,我們正好利用控制長按的flag來進行區分。
至此分析完畢,我想我們該開始寫代碼了。
1 void MyClass::keyReleaseEvent(QKeyEvent *event) { 2 switch (event->key()) 3 { 4 case Qt::Key_A: 5 //是否是長按可以從release中直接判斷 6 if (!event->isAutoRepeat()) { 7 //LongPress_初始值為false,如果非長按執行單擊或雙擊動作判斷 8 //如果長按會在長按里將其置true,在最后的R(F)里就不會執行單擊、雙擊判斷的動作 9 if (!LongPress_) { 10 if (!timer_->isActive()) { 11 timer_->start(400); 12 KeyFlag_ = kKey_A; 13 } 14 ClickCount_++; 15 if (ClickCount_ == 2){ 16 timer_->stop(); 17 ClickCount_ = 0; 18 cout << "this is A doubleclick" << endl; 19 DoDoubleThings();//執行按鍵A雙擊動作 20 } 21 } 22 LongPress_ = false;//置false 23 } 24 else{ 25 if (!LongPress_) { 26 cout << "this is longpress" << endl; 27 //限定長按只執行一次,最后會在R(F)里將LongPress_重新置false 28 LongPress_ = true; 29 DoLongPressThings(); 30 } 31 } 32 break; 33 34 case Qt::Key_S: 35 break; 36 case Qt::Key_D: 37 break; 38 case Qt::Key_F: 39 break; 40 case Qt::Key_J: 41 break; 42 case Qt::Key_K: 43 break; 44 default: 45 break; 46 } 47 }
親測有效,如有不同方法,歡迎討論,或是有更好的方法,也請不吝分享!
