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全屏这种。不过对于简单的上下左右,还有字母数字这些键盘按键的监听,我们还是应该要学会的。在一些拖拽进度条、音量等等的需求中,键盘按键往往可以提供与鼠标不一样的精细操作。

相关推荐
nnerddboy2 小时前
FPGA自学笔记(正点原子ZYNQ7020):1.Vivado软件安装与点灯
笔记·fpga开发
Chunyyyen2 小时前
【第十六周】自然语言处理的学习笔记01
笔记·学习·自然语言处理
我命由我123452 小时前
Photoshop - Photoshop 工具栏(5)多边套索工具
笔记·学习·ui·职场和发展·photoshop·ps·美工
龙木之森3 小时前
纯 C++ 开发的 Telegram Bot 框架
c++·tui·telegram·bot
hqwest4 小时前
QT肝8天09--用户列表
开发语言·c++·qt·上位机·qt开发
_dindong4 小时前
动规:回文串问题
笔记·学习·算法·leetcode·动态规划·力扣
眠りたいです4 小时前
基于脚手架微服务的视频点播系统-脚手架开发部分-brpc中间件介绍与使用及二次封装
c++·微服务·中间件·rpc·架构·brpc
Zwb2997924 小时前
Day 30 - 错误、异常与 JSON 数据 - Python学习笔记
笔记·python·学习·json
不太可爱的叶某人5 小时前
【学习笔记】kafka权威指南——第7章 构建数据管道(7-10章只做了解)
笔记·学习·kafka