QT實現鍵盤複用:單擊、雙擊、長按

由於項目需求,需要實現基於鍵盤按鍵的複用,查了很多資料都不滿足我的需求,其中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動作,

  1. 第一次執行press動作,此時QKeyEvent 不認爲你在長按,而在release時,QKeyEvent 已經開始認爲你在長按了;
  2. 第二次到倒數第二次QKeyEvent 認爲你都在長按;
  3. 最後一次,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;
    }   
}

親測有效,如有不同方法,歡迎討論,或是有更好的方法,也請不吝分享!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章