由於項目需求,需要實現基於鍵盤按鍵的複用,查了很多資料都不滿足我的需求,其中Mango的吐槽一下Qt的按鍵消息響應對我啓發很大,他闡述了關於按鍵長按的問題,我的測試結果和他有些出入,但總體思路是一樣的,也歡迎大家指正。下面來說一下具體的實現過程。
鍵盤按鍵單擊、雙擊
首先鍵盤按鍵的單擊、雙擊實現,沒錯!就是用的QTimer,一說到這估計大部分人都知道怎麼回事了,但這裏也有個誤區,那就是如何區分單擊和雙擊的問題,這也是我實現過程中遇到的問題。我最開始的做法是根據按下和釋放的時間間隔來區分的,現在看來這顯然是不對的,但當時腦袋可能蒙圈了(>_<),這樣是無法準確的區分單擊和雙擊的,應該用兩次按鍵的時間間隔來區分,這裏在按下、或釋放裏實現都是可以的,我最後選擇在釋放裏實現,後面再說原因。如果誰不清楚按下、釋放什麼意思,自己去查qt幫助文檔吧。。。
頭文件裏定義幾個相關變量
//MyClass.h
QTimer* timer_;
bool LongPress_;//後面用到
int ClickCount_;//按鍵計數
KeyFlag KeyFlag_;//枚舉
enum KeyFlag
{
kKey_NULL,
kKey_A,
kKey_S,
kKey_D,
kKey_F,
kKey_J,
kKey_K
};
構造函數連接timeout信號到單擊函數
//MyClass.cpp
timer_ = new QTimer(this);
connect(timer_, SIGNAL(timeout()), this, SLOT(KeyOneClick()));
ClickCount_ = 0;
LongPress_ = false;
KeyFlag_ = kKey_NULL;
重寫void keyReleaseEvent(QKeyEvent *event)
void MyClass::keyReleaseEvent(QKeyEvent *event) {
switch (event->key())
{
case Qt::Key_A:
if (!timer_->isActive()) {//計數期間,如果QTimer已開始,則不重新開始
timer_->start(400);//400ms是判斷雙擊的時間間隔,不唯一,可根據實際情況改變
KeyFlag_ = kKey_A;//單擊函數的按鍵標識
}
ClickCount_++;//點擊計數,在400ms內如果點擊兩次認爲是雙擊
if (ClickCount_ == 2){
timer_->stop();//停止計時
ClickCount_ = 0;//計數清零
cout << "this is A double click" << endl;
DoDoubleThings();//執行按鍵A雙擊動作
}
break;
case Qt::Key_S://注意到沒?我實現的都是字母鍵,其他鍵是不一樣的
break;
case Qt::Key_D:
break;
case Qt::Key_F:
break;
case Qt::Key_J:
break;
case Qt::Key_K:
break;
default:
break;
}
}
單擊函數,在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
下面看一下長按會發生什麼吧。
void MyClass::keyPressEvent(QKeyEvent *event) {
switch (event->key())
{
case Qt::Key_A:
if (!event->isAutoRepeat()) {
//非長按輸出press、not repeat等關鍵詞
cout << "this is A press: not Auto Repeat" << endl;
}
else{
//長按輸出press、is repeat等關鍵詞
cout << "this is A press: is Auto Repeat" << endl;
}
break;
default:
break;
}
}
void MyClass::keyReleaseEvent(QKeyEvent *event) {
switch (event->key())
{
case Qt::Key_A:
if (!event->isAutoRepeat()) {
//非長按輸出release、not repeat等關鍵詞
cout << "this is A release: not Auto Repeat" << endl;
}
else{
//非長按輸出release、is repeat等關鍵詞
cout << "this is A release: is Auto Repeat" << endl;
}
break;
default:
break;
}
}
運行結果用我的方式表示爲: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來進行區分。
至此分析完畢,我想我們該開始寫代碼了。
void MyClass::keyReleaseEvent(QKeyEvent *event) {
switch (event->key())
{
case Qt::Key_A:
//是否是長按可以從release中直接判斷
if (!event->isAutoRepeat()) {
//LongPress_初始值爲false,如果非長按執行單擊或雙擊動作判斷
//如果長按會在長按裏將其置true,在最後的R(F)裏就不會執行單擊、雙擊判斷的動作
if (!LongPress_) {
if (!timer_->isActive()) {
timer_->start(400);
KeyFlag_ = kKey_A;
}
ClickCount_++;
if (ClickCount_ == 2){
timer_->stop();
ClickCount_ = 0;
cout << "this is A doubleclick" << endl;
DoDoubleThings();//執行按鍵A雙擊動作
}
}
LongPress_ = false;//置false
}
else{
if (!LongPress_) {
cout << "this is longpress" << endl;
//限定長按只執行一次,最後會在R(F)裏將LongPress_重新置false
LongPress_ = true;
DoLongPressThings();
}
}
break;
case Qt::Key_S:
break;
case Qt::Key_D:
break;
case Qt::Key_F:
break;
case Qt::Key_J:
break;
case Qt::Key_K:
break;
default:
break;
}
}
親測有效,如有不同方法,歡迎討論,或是有更好的方法,也請不吝分享!