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;
    }   
}

亲测有效,如有不同方法,欢迎讨论,或是有更好的方法,也请不吝分享!

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