QT实现键盘复用:单击、双击、长按

转自: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动作,

  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来进行区分。

至此分析完毕,我想我们该开始写代码了。

 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 }

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

posted @ 2021-01-11 17:25  阳光下的小土豆  阅读(1853)  评论(0编辑  收藏  举报