想写个判断哪些方向键同时按下的逻辑,结果发现 Qt 的按键事件只能取到单个键值,而 QKeyEvent::modifiers() 又只能获取 Ctrl 或者 Shift 这种辅助按键。一番百度之后,发现可以使用容器保存按键值,在 keyPressEvent 添加按键值,在 keyReleaseEvent 移除按键值。
不过单单处理按键的按下和弹起还不够,还需要注意一些事项:
A.弹起其中一个按键时,按键事件的触发会停顿一下。所以,不能在按键事件里直接处理逻辑,需要加个定时器来遍历我们的键值容器;
B.按键长按时会重复触发按下和弹起两个事件,这可能导致我们的键值容器在遍历时值不对(比如长按触发按键事件,导致定时器触发时我们的容器正好弹出键值就为空了)。所以,我们要单独判断下 QKeyEvent 的 isAutoRepeat,如果是自动重复触发就不处理。
实现效果,以移动方块为例(两个方向键同时按下就是斜着移动):
分别展示 QWidget 和 QML 的示例。
#ifndef UNIT4MOVE_H
#define UNIT4MOVE_H
#include
#include
#include
#include
#include
//按键移动物体
class Unit4Move : public QOpenGLWidget, protected QOpenGLFunctions_4_0_Compatibility
{
Q_OBJECT
public:
explicit Unit4Move(QWidget *parent = nullptr);
protected:
//设置OpenGL资源和状态。在第一次调用resizeGL或paintGL之前被调用一次
void initializeGL() override;
//渲染OpenGL场景,每当需要更新小部件时使用
void paintGL() override;
//设置OpenGL视口、投影等,每当尺寸大小改变时调用
void resizeGL(int width, int height) override;
//按键按下
void keyPressEvent(QKeyEvent *event) override;
//按键释放
void keyReleaseEvent(QKeyEvent *event) override;
//设置切换显示的时候获取焦点
void showEvent(QShowEvent *event) override;
private:
//顶点列表
QList vertexList;
//移动
float xOffset=0;
float yOffset=0;
//当前按键按下的列表
//因为Qt按键事件只能判断出一个键值和辅助键值的组合,
//所以自己保存按下的按键
QSet pressedKeys;
//刷新定时器
QTimer *updateTimer;
};
#endif // UNIT4MOVE_H
#include "Unit4Move.h"
#include
#include
#include
#include
#include
Unit4Move::Unit4Move(QWidget *parent)
: QOpenGLWidget(parent)
{
//默认没得焦点,没法接收按键
setFocusPolicy(Qt::StrongFocus);
//多个按键按下还有一个问题,就是最后那个按键弹起后就不会重复触发了
//所以刷新我们可以用定时器来判断容器列表,release时判断为空就关,press就开
updateTimer=new QTimer(this);
connect(updateTimer,&QTimer::timeout,[this]{
//qDebug()<<"timeout"<stop();
return;
}
for(int key:pressedKeys)
{
switch (key) {
case Qt::Key_Up:
yOffset+=0.1f;
break;
case Qt::Key_Down:
yOffset-=0.1f;
break;
case Qt::Key_Left:
xOffset-=0.1f;
break;
case Qt::Key_Right:
xOffset+=0.1f;
break;
default:
break;
}
}
update();
});
}
void Unit4Move::initializeGL()
{
//此为Qt接口:为当前上下文初始化OpenGL函数解析
initializeOpenGLFunctions();
//初始化
//矩形分为两个三角
vertexList=QList{
{0.5f,0.5f,0.0f},
{-0.5f,0.5f,0.0f},
{-0.5f,-0.5f,0.0f},
{-0.5f,-0.5f,0.0f},
{0.5f,-0.5f,0.0f},
{0.5f,0.5f,0.0f} };
//窗口清除的颜色
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
void Unit4Move::paintGL()
{
//清除颜色缓冲区
glClear(GL_COLOR_BUFFER_BIT);
//【给opengl设置矩阵】
//当前矩阵为模型矩阵
glMatrixMode(GL_MODELVIEW);
//重置当前指定的矩阵为单位矩阵,恢复坐标系
//OpenGL是状态机,会保存之前的状态
glLoadIdentity();
glOrtho(-5,5,-5,5,-15,15);
glTranslatef(xOffset,yOffset,0);
glRotatef(45,0,0,1);
//填充颜色
glColor3f(0.0f,0.5f,1.0f);
//绘制三角(坐标范围默认为[-1,1])
glBegin(GL_TRIANGLES);
for(const QVector3D &vertex:vertexList){
glVertex3f(vertex.x(),vertex.y(),vertex.z());
}
glEnd();
//绘制文本
QPainter painter(this);
painter.setPen(QColor(255,255,255));
painter.drawText(20,40,"点击获取焦点后,按方向键移动!");
}
void Unit4Move::resizeGL(int width, int height)
{
//视口,靠左下角缩放
glViewport(0,0,width,height);
}
void Unit4Move::keyPressEvent(QKeyEvent *event)
{
QOpenGLWidget::keyPressEvent(event);
//按键按下,key值放入容器,如果是长按触发的repeat就不判断
if(!event->isAutoRepeat())
pressedKeys.insert(event->key());
//判断是否运行,不然一直触发就一直不能timeout
if(!updateTimer->isActive())
updateTimer->start(100);
}
void Unit4Move::keyReleaseEvent(QKeyEvent *event)
{
QOpenGLWidget::keyReleaseEvent(event);
//按键释放,从容器中移除,如果是长按触发的repeat就不判断
if(!event->isAutoRepeat())
pressedKeys.remove(event->key());
if(pressedKeys.isEmpty()){
updateTimer->stop();
}
}
void Unit4Move::showEvent(QShowEvent *event)
{
QOpenGLWidget::showEvent(event);
setFocus();
}
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
visible: true
width: 640
height: 480
title: qsTr("GongJianBo")
//存储当前按下的键值,使用ES6的集合类型
property variant pressedKeys:new Set()
Rectangle{
id: item
//anchors.centerIn: parent
x:100
y:100
width: 100
height: 100
color: "green"
focus: true
//按键按下
Keys.onPressed: {
//键值放入set
if(!event.isAutoRepeat){
pressedKeys.add(event.key)
}
if(!timer.running){
timer.start()
}
}
//按键释放
Keys.onReleased: {
//键值弹出set
if(!event.isAutoRepeat){
pressedKeys.delete(event.key)
}
if(pressedKeys.size<=0){
timer.stop()
}
}
}
Timer{
id:timer
repeat: true
interval: 50
onTriggered: {
//console.log(pressedKeys.size)
if(pressedKeys.size<=0){
timer.stop()
return
}
//遍历存储的键值,实现同时处理多个按键按下状态
for(let key of pressedKeys)
{
switch (key) {
case Qt.Key_Up:
item.y-=5;
break;
case Qt.Key_Down:
item.y+=5;
break;
case Qt.Key_Left:
item.x-=5;
break;
case Qt.Key_Right:
item.x+=5;
break;
default:
break;
}
}
}
}
}