Qt 及 QML 处理多个按键同时按下

1.思路

想写个判断哪些方向键同时按下的逻辑,结果发现 Qt 的按键事件只能取到单个键值,而 QKeyEvent::modifiers() 又只能获取 Ctrl 或者 Shift 这种辅助按键。一番百度之后,发现可以使用容器保存按键值,在 keyPressEvent 添加按键值,在 keyReleaseEvent 移除按键值。

不过单单处理按键的按下和弹起还不够,还需要注意一些事项:

A.弹起其中一个按键时,按键事件的触发会停顿一下。所以,不能在按键事件里直接处理逻辑,需要加个定时器来遍历我们的键值容器;

B.按键长按时会重复触发按下和弹起两个事件,这可能导致我们的键值容器在遍历时值不对(比如长按触发按键事件,导致定时器触发时我们的容器正好弹出键值就为空了)。所以,我们要单独判断下 QKeyEvent 的 isAutoRepeat,如果是自动重复触发就不处理。

实现效果,以移动方块为例(两个方向键同时按下就是斜着移动):

分别展示 QWidget 和 QML 的示例。

2.QWidget 代码

#ifndef UNIT4MOVE_H
#define UNIT4MOVE_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_0_Compatibility>
#include <QMatrix4x4>
#include <QVector3D>
#include <QTimer>

//按键移动物体
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<QVector3D> vertexList;
    //移动
    float xOffset=0;
    float yOffset=0;
    //当前按键按下的列表
    //因为Qt按键事件只能判断出一个键值和辅助键值的组合,
    //所以自己保存按下的按键
    QSet<int> pressedKeys;
    //刷新定时器
    QTimer *updateTimer;
};

#endif // UNIT4MOVE_H

 

#include "Unit4Move.h"

#include <QPainter>
#include <QVector3D>
#include <QtMath>
#include <QKeyEvent>
#include <QDebug>

Unit4Move::Unit4Move(QWidget *parent)
    : QOpenGLWidget(parent)
{
    //默认没得焦点,没法接收按键
    setFocusPolicy(Qt::StrongFocus);

    //多个按键按下还有一个问题,就是最后那个按键弹起后就不会重复触发了
    //所以刷新我们可以用定时器来判断容器列表,release时判断为空就关,press就开
    updateTimer=new QTimer(this);
    connect(updateTimer,&QTimer::timeout,[this]{
        //qDebug()<<"timeout"<<pressedKeys;
        if(pressedKeys.isEmpty()){
            updateTimer->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<QVector3D>{
    {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();
}

3.QML 代码

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

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龚建波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值