一、简介
QKeyEvent 类用来描述一个键盘事件。当键盘按键被按下或者被释放时,键盘事件便会被发送给拥有键盘输人焦点的部件。
QKeyEvent 的 key() 函数可以获取具体的按键,对于 Qt 中给定的所有按键,可以在帮助中查看 Qt: :Key 关键字。需要特别说明的是,回车键在这里是 Qt::Key_Return;键盘上的一些修饰键,比如 Ctrl 和 Shift 等, 这里需要使用 QKeyEvent 的 modifiers() 函数来获取,可以在帮助中使用 Qt:: KeyboardModifier 关键字来査看所有的修饰键。
QKeyEvent 有两个键盘事件成员函数,在头文件.h中进行声明:
#include <QKeyEvent>
protected:
void keyPressEvent(QKeyEvent *event); //键盘按下事件
void keyReleaseEvent(QKeyEvent *event); //键盘松开事件
二、常用操作
下面是些常用操作:
// 键盘按下事件
void Widget::keyPressEvent(QKeyEvent * event)
{
// 普通键
switch (event->key())
{
// ESC键
case Qt::Key_Escape:
qDebug() <<"ESC";
break;
// 回车键
case Qt::Key_Return:
qDebug() <<"Enter";
break;
// F1键
case Qt::Key_F1:
qDebug() <<"F1";
break;
}
// 两键组合
if(event->modifiers() == Qt::ControlModifier) { // 如果按下了CTRL键
if(event->key() == Qt::Key_M){
qDebug()<<"CTRL + M";
}
}
if(event->modifiers() == Qt::AltModifier) { // 如果按下了ALT键
if(event->key() == Qt::Key_M)
qDebug()<<"ALT + M";
}
if(event->modifiers() == Qt::ShiftModifier){ // 如果按下了Shift键
if(event->key() == Qt::Key_M)
qDebug()<<"Shift + M";
}
// 三键组合Shift + Ctrl + A的实现
if (event->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier) && event->key() == Qt::Key_A) {
qDebug() << "CTRL + Shift + A";
}
}
// 键盘释放事件
void Widget::keyReleaseEvent(QKeyEvent *event)
{
// 方向UP键
if(event->key() == Qt::Key_Up)
{
qDebug() << "release: "<< "up";
}
}
分别按下 ESC、Enter、CTRL + M、ALT + M 等键,“应用程序输出”窗口输出如下:
ESC
Enter
CTRL + M
ALT + M
release: up
三、按键与自动重复
自动重复是指按下键盘上的键(修饰键除外)不放时,会不断重复的发送键按下事件,Qt 默认是启用自动重复的,若要实现类似按键 A+D 之类的快捷键,就需要关闭自动重复。可使用如下方法来关闭自动重复:
// 若自动重复则什么也不做
if(QKeyEvent::isAutoRepeat())
return;
Qt 的键盘事件整体表现为,按住一个键时:
1、第一次触发 keyPressEvent(),isAutoRepeat() 返回 false
2、没有触发 keyReleaseEvent(),停顿一会
3、再一次触发 keyPressEvent(),isAutoRepeat() 返回true
4、触发 keyReleaseEvent()
5、若没松开按键,isAutoRepeat() 返回 true,返回第 3 步;若松开按键,isAutoRepeat() 返回 false
四、键盘捕获
可以只指定窗口中的某个控件捕获键盘事件,使其他控件无法获得键盘事件,示例如下。
MyButton.h:
#ifndef MYBUTTON_H
#define MYBUTTON_H
#include <QWidget>
#include <QPushButton>
#include <QKeyEvent>
#include <QDebug>
class MyButton : public QPushButton
{
Q_OBJECT
public:
explicit MyButton(QWidget *parent = nullptr);
protected:
void keyPressEvent(QKeyEvent *event);
};
#endif // MYBUTTON_H
MyButton.cpp
#include "MyButton.h"
MyButton::MyButton(QWidget *parent) : QPushButton(parent)
{
}
void MyButton::keyPressEvent(QKeyEvent *event)
{
qDebug() << "button键盘按键事件:" << this->objectName();
QWidget *ww = keyboardGrabber(); // 返回正在捕获键盘输入的部件,若没有则返回 0
qDebug() << "正在捕获的控件:" << ww;
if(event->key() == Qt::Key_Q) {
qDebug() << "按下了Q键";
this->releaseKeyboard(); // 释放捕获的键盘输入
}
}
Widget.h:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QDebug>
#include <QKeyEvent>
#include "MyButton.h"
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
protected:
void keyPressEvent(QKeyEvent *event);
private:
MyButton* m_pBtn1;
MyButton* m_pBtn2;
};
#endif // WIDGET_H
Widget.cpp:
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
this->resize(300,400);
// 初始化按钮1
m_pBtn1=new MyButton();
m_pBtn1->setParent(this);
m_pBtn1->setText("AAA");
m_pBtn1->move(10,10);
m_pBtn1->resize(100,100);
m_pBtn1->setObjectName("aaa");
// 初始化按钮2
m_pBtn2=new MyButton();
m_pBtn2->setParent(this);
m_pBtn2->setText("BBB");
m_pBtn2->move(150,10);
m_pBtn2->resize(100,100);
m_pBtn2->setObjectName("bbb");
// 指定控件捕获键盘
m_pBtn1->grabKeyboard();
/*使按钮 AAA 捕获键盘,此时产生的键盘事件都只会发送给按钮 AAA,也就是说
其他部件无法获得键盘事件。
只有可见的部件才能捕获键盘输入,若 isVisible()返回 false,则该部件不能调用grabKeyboard()函数
*/
}
void Widget::keyPressEvent(QKeyEvent *event)
{
Q_UNUSED(event);
qDebug()<<"Widget发生键盘事件";
}
按下任何键,发现只有按钮 AAA 才能获得键盘事件,按钮 BBB 无法获得键盘事件。
五、键盘按键单击、双击
首先键盘按键的单击、双击实现用的 QTimer,一说到这估计大部分人都知道怎么回事了,但这里也有个误区,那就是如何区分单击和双击的问题。这里使用两次按键的时间间隔来区分,这里在按下、或释放里实现都是可以的,这里我最后选择在释放里实现,后面再说原因。
头文件里定义几个相关变量:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QKeyEvent>
#include <QTimer>
#include <QDebug>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
protected:
void keyReleaseEvent(QKeyEvent *event);
private:
QTimer* m_pTimer; // Ctrl双击定时器
int m_nClickCnt = 0; // 点击次数
bool m_bLongPress = false; // 是否为长按
};
#endif // WIDGET_H
再看键盘按键单击、双击实现:
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
// 定时器
m_pTimer = new QTimer(this);
connect(m_pTimer, &QTimer::timeout, [=]{
m_nClickCnt = 0; // 计数清零
m_pTimer->stop(); // 停止计时
qDebug() << "键盘单击";
});
}
void Widget::keyReleaseEvent(QKeyEvent *event)
{
Q_UNUSED(event);
if(event->key() == Qt::Key_A) {
// 计数期间,如果QTimer已开始,则不重新开始
if(!m_pTimer->isActive())
m_pTimer->start(500); // 500ms是判断双击的时间间隔,不唯一,可根据实际情况改变
m_nClickCnt++; // 点击计数,在500ms内如果点击两次认为是双击
if(m_nClickCnt >= 2) {
m_nClickCnt = 0; // 计数清零
m_pTimer->stop(); // 停止计时
qDebug() << "键盘双击";
}
}
}
单击,在 500ms 内未达到双击次数,也就是未执行 timer_->stop(); 时间耗尽触发 timeout 信号,执行单击动作。这里提一下 stop() 函数,QTimer 执行 start(n) 后,如果不 stop(),那它会循环执行。
六、键盘按键长按
此实现键盘单击和双击复用,那么我们再来看一下长按怎么处理呢?
为了区分是否是长按,QKeyEvent 提供了一个 isAutoRepeat() 函数自动检测按键是否长按
长按返回 true
非长按返回 false
前面提到单击和双击的区分,其实在void keyPressEvent(QKeyEvent *event)、void keyReleaseEvent(QKeyEvent *event)函数里都可以,反正都是记录时间差,press-press 或 release-release 没分别,那最后为什么选择在按键释放函数里实现呢?
问题就在还得同时实现长按功能,刚刚分析得出无论你长按还是非长按,第一次的 press 动作他都是按下非长按的,如果在void keyPressEvent(QKeyEvent *event)里实现,那长按必然会附加一次单击,这当然不是我们想要的;
至此分析完毕,我想我们该开始写代码了。
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
// 定时器
m_pTimer = new QTimer(this);
connect(m_pTimer, &QTimer::timeout, [=]{
m_nClickCnt = 0; // 计数清零
m_pTimer->stop(); // 停止计时
qDebug() << "键盘单击";
});
}
void Widget::keyReleaseEvent(QKeyEvent *event)
{
Q_UNUSED(event);
if(event->key() == Qt::Key_A) { // Qt::Key_Control经实测,长按永远不会使isAutoRepeat()为true
// 是否是长按可以从release中直接判断
if (!event->isAutoRepeat()) {
// LongPress_初始值为false,如果非长按执行单击或双击动作判断
// 如果长按会在长按里将其置true,在最后的Relese(非长按)里就不会执行单击、双击判断的动作
if (!m_bLongPress) {
if (!m_pTimer->isActive()) {
m_pTimer->start(500);
}
m_nClickCnt++;
if (m_nClickCnt == 2){
m_nClickCnt = 0; // 计数清零
m_pTimer->stop(); // 停止计时
qDebug() << "键盘双击";
}
}
m_bLongPress = false; // 置false
}
else{
if (!m_bLongPress) {
qDebug() << "键盘长按";
// 限定长按只执行一次,最后会在Relese(非长按)里将LongPress_重新置false
m_bLongPress = true;
}
}
}
}