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的描述:
这里的枚举太多了,我也只是截取出一部分的。
接下来,我们将通过简单的实例,来学习如何处理按键事件。
二、特定信号和通用信号
直接引用信号处理器试试:
Window {visible: truewidth: 640height: 480title: qsTr("QmlKeysAttachedProperty")Rectangle{id: containedRectIdanchors.centerIn: parentwidth: 300height: 50color: "dodgerblue"focus: trueKeys.onDigit5Pressed: {console.log("press 5")}}
}
Keys.onDigit5Pressed: {console.log("press 5")}
就这么简单,你甚至不需要像MouseArea那样创建一个鼠标组件,还要考虑它的范围大小。
这里我分别点击键盘上方和右侧数字键盘中的“5”,都能触发打印。
但这种单一按键的信号处理太麻烦了,所以我们使用press这个通用信号看看。
Keys.onPressed: (event)=> {if(event.key === Qt.Key_5){console.log("press 5")}}
效果是正常的,也能打印信息。
如果这两个处理器都写了呢?你会发现Qt会优先选择特定信号。
这个时候如果你还是想触发通用信号,那你可以选择在特定信号中不接受信号,继续向下传递:
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这种形式,意味着要同时按下多个按键。这个时候,我们可以稍作判断:
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修饰符。
四、用键盘移动方块
之前我们已经学习过如果通过鼠标拖动来移动方块,这次我们试试通过键盘来实现:
Window {visible: truewidth: 640height: 480title: qsTr("QmlMouseArea")Rectangle{id: dragContainerIdwidth:parent.widthheight:400color: "beige"y: 100focus: trueRectangle{id: moveableRectIdx: 0y: 0width: 50height: widthcolor: "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,应该是底层机制的一些问题,事件刷新频率什么的。
于是我让它直接给我修改了段代码,跑起来效果还不错,就是我目前还没完全理解,但还是放上来吧:
import QtQuick 2.15
import QtQuick.Window 2.15Window {visible: truewidth: 640height: 480title: qsTr("Smooth Key Move")Rectangle {id: dragContainerIdwidth: parent.widthheight: 400color: "beige"focus: true/* 可移动矩形 */Rectangle {id: moveableRectIdx: 0; y: 0width: 50; height: widthcolor: "blue"}/* 位图记录当前按下的方向 */property int pressedKeys: 0readonly property int key_up: 1 << 0readonly property int key_down: 1 << 1readonly property int key_left: 1 << 2readonly property int key_right: 1 << 3/* 16 ms 刷新计时器 → ~60 FPS 移动 */Timer {interval: 16repeat: truerunning: dragContainerId.pressedKeys !== 0onTriggered: {const step = 4; // 每帧像素if (dragContainerId.pressedKeys & dragContainerId.key_up) moveableRectId.y -= stepif (dragContainerId.pressedKeys & dragContainerId.key_down) moveableRectId.y += stepif (dragContainerId.pressedKeys & dragContainerId.key_left) moveableRectId.x -= stepif (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全屏这种。不过对于简单的上下左右,还有字母数字这些键盘按键的监听,我们还是应该要学会的。在一些拖拽进度条、音量等等的需求中,键盘按键往往可以提供与鼠标不一样的精细操作。