由于项目需求,需要实现基于键盘按键的复用,查了很多资料都不满足我的需求,其中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;
}
}
亲测有效,如有不同方法,欢迎讨论,或是有更好的方法,也请不吝分享!