QML学习笔记(二十四)QML的Keys附加属性

前言

本节我们将学习键盘行为相关的Keys附加属性,其区别于QWidget的key事件,可以灵活给qml组件添加key的附加属性。

一、了解Keys

查阅帮助文档:

详细描述

所有视觉图元都支持通过Keys attached属性进行键处理。按键可以通过onPressed和onRelease信号属性进行处理。

信号属性有一个KeyEvent参数,名为event,其中包含事件的详细信息。如果处理了密钥,则应将event.accepted设置为true,以防止事件在项目层次结构中向上传播。

简单来说,Keys属于QML中的一种附加机制,可以为某个元素赋予键盘监听的能力。例如默认情况下,Rectangle不知道如何处理按键,此时给他设置一个Keys附加属性,就可以处理按键事件了。

再看一下它的信号:

可以看到一些digit0~9这些数字按键,也又空格spacePressed这种。如果需要更复杂的按键,可以使用pressed信号,通过其event获取。

再看一下KeyEvent的描述:


这里的枚举太多了,我也只是截取出一部分的。

接下来,我们将通过简单的实例,来学习如何处理按键事件。

二、特定信号和通用信号

直接引用信号处理器试试:

cpp 复制代码
Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("QmlKeysAttachedProperty")

    Rectangle{
        id: containedRectId
        anchors.centerIn: parent
        width: 300
        height: 50
        color: "dodgerblue"
        focus: true

        Keys.onDigit5Pressed: {
            console.log("press 5")
        }
    }
}
cpp 复制代码
		Keys.onDigit5Pressed: {
            console.log("press 5")
        }

就这么简单,你甚至不需要像MouseArea那样创建一个鼠标组件,还要考虑它的范围大小。

这里我分别点击键盘上方和右侧数字键盘中的"5",都能触发打印。

但这种单一按键的信号处理太麻烦了,所以我们使用press这个通用信号看看。

cpp 复制代码
        Keys.onPressed: (event)=> {
            if(event.key === Qt.Key_5){
                console.log("press 5")
            }
        }

效果是正常的,也能打印信息。

如果这两个处理器都写了呢?你会发现Qt会优先选择特定信号。

这个时候如果你还是想触发通用信号,那你可以选择在特定信号中不接受信号,继续向下传递:

cpp 复制代码
        Keys.onDigit5Pressed: (event)=> {
            console.log("press 5")
            event.accepted = false
        }

        Keys.onPressed: (event)=> {
            if(event.key === Qt.Key_5){
                console.log("press 5...")
            }
        }

另外提一嘴,press只是其中一种行为,还有release这种松开的信号,实现形式是一样的。

三、处理Ctrl+按键

有一种比较常见的按键情况,是Ctrl+x这种形式,意味着要同时按下多个按键。这个时候,我们可以稍作判断:

cpp 复制代码
 Keys.onDigit5Pressed: (event)=> {
     if(event.modifiers === Qt.ControlModifier){
         console.log("press 5 with ctrl")
         event.accepted = false
     }else{                
         console.log("press 5")
         event.accepted = false
     }
}

modifiers是修饰符的意思,Qt.ControlModifier代表当前按下了Ctrl修饰符。

四、用键盘移动方块

之前我们已经学习过如果通过鼠标拖动来移动方块,这次我们试试通过键盘来实现:

cpp 复制代码
Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("QmlMouseArea")

    Rectangle{
        id: dragContainerId
        width:parent.width
        height:400
        color: "beige"
        y: 100
        focus: true

        Rectangle{
            id: moveableRectId
            x: 0
            y: 0
            width: 50
            height: width
            color: "blue"
            onXChanged: {
                console.log("The x position is: " + x)
            }
        }

        Keys.onPressed: (event)=> {
            if((event.key === Qt.Key_Up) || (event.key === Qt.Key_W)){
                console.log("press up...")
                moveableRectId.y -= 10
            }else if((event.key === Qt.Key_Down) || (event.key === Qt.Key_S)){
                console.log("press down...")
                moveableRectId.y += 10
            }else if((event.key === Qt.Key_Left) || (event.key === Qt.Key_A)){
                console.log("press left...")
                moveableRectId.x -= 10
            }else if((event.key === Qt.Key_Right) || (event.key === Qt.Key_D)){
                console.log("press right...")
                moveableRectId.x += 10
            }
        }
    }
}

可以看到,这里就是用了简单的条件判断,代码层级比较简单,直接看效果:

可以实现,甚至长按的时候还可以一直移动,就是有些不太灵敏。

问了一下kimi,应该是底层机制的一些问题,事件刷新频率什么的。

于是我让它直接给我修改了段代码,跑起来效果还不错,就是我目前还没完全理解,但还是放上来吧:

cpp 复制代码
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Smooth Key Move")

    Rectangle {
        id: dragContainerId
        width: parent.width
        height: 400
        color: "beige"
        focus: true

        /* 可移动矩形 */
        Rectangle {
            id: moveableRectId
            x: 0; y: 0
            width: 50; height: width
            color: "blue"
        }

        /* 位图记录当前按下的方向 */
        property int pressedKeys: 0
        readonly property int key_up:    1 << 0
        readonly property int key_down:  1 << 1
        readonly property int key_left:  1 << 2
        readonly property int key_right: 1 << 3

        /* 16 ms 刷新计时器 → ~60 FPS 移动 */
        Timer {
            interval: 16
            repeat: true
            running: dragContainerId.pressedKeys !== 0
            onTriggered: {
                const step = 4;                       // 每帧像素
                if (dragContainerId.pressedKeys & dragContainerId.key_up)    moveableRectId.y -= step
                if (dragContainerId.pressedKeys & dragContainerId.key_down)  moveableRectId.y += step
                if (dragContainerId.pressedKeys & dragContainerId.key_left)  moveableRectId.x -= step
                if (dragContainerId.pressedKeys & dragContainerId.key_right) moveableRectId.x += step
            }
        }

        /* 按键按下:打开对应方向位 */
        Keys.onPressed: (event) => {
            switch (event.key) {
            case Qt.Key_Up:   case Qt.Key_W:
                dragContainerId.pressedKeys |= dragContainerId.key_up;    break;
            case Qt.Key_Down: case Qt.Key_S:
                dragContainerId.pressedKeys |= dragContainerId.key_down;  break;
            case Qt.Key_Left: case Qt.Key_A:
                dragContainerId.pressedKeys |= dragContainerId.key_left;  break;
            case Qt.Key_Right:case Qt.Key_D:
                dragContainerId.pressedKeys |= dragContainerId.key_right; break;
            }
            event.accepted = true;   // 吃掉事件,避免系统 repeat 干扰
        }

        /* 按键释放:关闭对应方向位 */
        Keys.onReleased: (event) => {
            switch (event.key) {
            case Qt.Key_Up:   case Qt.Key_W:
                dragContainerId.pressedKeys &= ~dragContainerId.key_up;    break;
            case Qt.Key_Down: case Qt.Key_S:
                dragContainerId.pressedKeys &= ~dragContainerId.key_down;  break;
            case Qt.Key_Left: case Qt.Key_A:
                dragContainerId.pressedKeys &= ~dragContainerId.key_left;  break;
            case Qt.Key_Right:case Qt.Key_D:
                dragContainerId.pressedKeys &= ~dragContainerId.key_right; break;
            }
            event.accepted = true;
        }
    }
}

五、总结

在我过往的工作当中,倒是没有对键盘有过特殊的需求,顶多接触过ESC全屏这种。不过对于简单的上下左右,还有字母数字这些键盘按键的监听,我们还是应该要学会的。在一些拖拽进度条、音量等等的需求中,键盘按键往往可以提供与鼠标不一样的精细操作。

相关推荐
学长毕设7 小时前
基于SpringBoot的青少年编程学习平台系统-附源码
spring boot·学习·青少年编程
choumin9 小时前
C++中万能引用与std::forward的作用和用法
c++·万能引用·std forward
下一站春风里10 小时前
位运算完整学习笔记:按位与(&)+ 异或(^)
开发语言·c++·算法
小小de风呀11 小时前
de风——【从零开始学习Linux】(九):进程基础 —— 从冯诺依曼体系到 fork 创建进程
linux·学习
李某学编程11 小时前
基于ARM的微机原理学习笔记(一)
arm开发·笔记·学习
Song_RsHt12 小时前
Redis 与微服务,什么场景下无可替代
java·redis·后端·学习·微服务
sbjdhjd12 小时前
AI产业周度动态:从“更强模型”走向“可执行、可治理、可计价”的系统竞争 (2026年10月19日—10月25日)
人工智能·笔记·ai·开源·gemini·信息差·claude code
努力努力再努力wz12 小时前
【C++高阶系列】C++20 协程底层原理:函数为什么能暂停?从状态机到 Task,彻底串起挂起、恢复与协程调度
开发语言·c++·c++20·1024程序员节
且随疾风前行.12 小时前
Paint Cavans高级绘制:[一]Paint画笔高级应用
android·ui
划破黑暗的第一缕曙光13 小时前
[C++]:4.模板
c++·template·模板·函数模版·类模版