ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

QML 按键事件篇:事件的传播与拦截

QML 按键事件篇:事件的传播与拦截 在实际项目中界面会出现一个输入框嵌入在一个面板里面板又套在一个更大的区域里。那按一下键盘按键事件到底发给谁会不会一层层都收到这就是事件传播的话题。QML 里键盘事件默认会从获得焦点的内层逐步向外层冒泡并且可以在任意一层用 event.accepted true 把它拦下来。这个 demo 用三层嵌套的矩形做演示内层、中层、外层。点内层后按任意键你会看到事件一层一层往外闪烁。打开拦截开关后按键只在内层触发不再外传。一个开关两种行为本文把按键事件的传播和拦截一次讲清。演示代码import QtQuick import QtQuick.Controls import QtQuick.Layouts FadeInAnimation { ColumnLayout { anchors.fill: parent anchors.margins: 20 spacing: 15 TitleSeparator { title: 事件传播与拦截 description: 按键事件从获得焦点的内层向外层逐级传播设置 event.accepted true 可阻止继续传播 } // 外层 Rectangle { id: outer Layout.fillWidth: true Layout.preferredHeight: 220 radius: 10 color: #FAFAFA border.color: outer.hit ? #E91E63 : #ccc border.width: 2 property bool hit: false SequentialAnimation { id: flashOuter running: false ColorAnimation { target: outer; property: color; to: #FFE0E0; duration: 80 } ColorAnimation { target: outer; property: color; to: #FAFAFA; duration: 300 } ScriptAction { script: outer.hit false } } Keys.onPressed: (event) { hit true; flashOuter.restart() } // 中层 Rectangle { id: middle anchors.centerIn: parent width: 240; height: 130 radius: 8 color: #FFF border.color: middle.hit ? #FF9800 : #ccc border.width: 2 property bool hit: false SequentialAnimation { id: flashMiddle running: false ColorAnimation { target: middle; property: color; to: #FFF3E0; duration: 80 } ColorAnimation { target: middle; property: color; to: #FFF; duration: 300 } ScriptAction { script: middle.hit false } } Keys.onPressed: (event) { hit true; flashMiddle.restart() } // 内层 —— 唯一可以获取焦点 Rectangle { id: inner anchors.centerIn: parent width: 160; height: 60 radius: 6 color: inner.activeFocus ? #EAF4FF : #FFF border.color: inner.activeFocus ? #1296FF : #ccc border.width: 2 focus: true property bool hit: false SequentialAnimation { id: flashInner running: false ColorAnimation { target: inner; property: color; to: #BBDEFB; duration: 80 } ColorAnimation { target: inner; property: color; to: #EAF4FF; duration: 300 } ScriptAction { script: inner.hit false } } Text { anchors.centerIn: parent text: 点击这里然后按任意键 font.pointSize: 10 color: inner.activeFocus ? #1296FF : #999 } MouseArea { anchors.fill: parent; onClicked: inner.forceActiveFocus() } Keys.onPressed: (event) { hit true; flashInner.restart() if (interceptSwitch.checked) event.accepted true // 拦截阻止向外传播 } } } } // 拦截开关 Switch { id: interceptSwitch Layout.alignment: Qt.AlignHCenter text: 内层拦截 (event.accepted true) checked: false } Text { Layout.alignment: Qt.AlignHCenter text: interceptSwitch.checked ? 拦截开启按键只触发内层蓝色闪烁 : 拦截关闭按键逐层传播蓝→橙→红闪烁 font.pointSize: 11 color: #666 } Item { Layout.fillHeight: true } } }关键逻辑解析为什么焦点在内层键盘发送的目标是拥有焦点的元素。三层矩形里只有最内层写了focus: true所以按键会先送到内层。中层、外层都没有焦点但它们照样能收到这个按键——这正是传播的体现也是理解本文的关键。事件从内向外冒泡按下键盘事件先到达内层inner内层处理完这里只是闪一下事件继续向外层冒泡——穿过中层middle再传到外层outer。三层都有各自的Keys.onPressed所以三层会依次触发视觉上就是内蓝→中橙→外红像涟漪一样连续闪。这就是键盘事件传播的默认路径焦点元素 → 它的父级 → 更外层一直往上冒泡。能不能收到取决于 ancestors 链传播能一路走到外层靠的是内层在结构上嵌套在中层、外层里面。信号会沿父链上传所以中层、外层即使没焦点也能收到。取消这个嵌套关系事件就到不了不相关的元素——传播是沿着父子链走的。拦截 event.accepted打开拦截开关后内层onPressed里多执行了这一行if (interceptSwitch.checked) event.accepted trueevent.accepted true的意思直白这个事件我已经收下了不用再往下传了。于是它在内层就被截断冒泡到此为止中层、外层再也收不到按键时只剩内层闪一下蓝光。两层概念的区分这里需要分清楚两个东西“收到和拦下”。事件默认是经过外层冒泡会经过它还是会被拦下内层accepted你可以在同一段传播里观察两种做法不改accepted事件一路冒泡全部能触发设了accepted它在外层的传播路径被切断。开关一开一关对比一目了然。闪现动画怎么实现的每层用一个SequentialAnimation做闪一下先快速变到高亮色80 毫秒再慢速回到原色300 毫秒。被按键命中时flashX.restart()重新播放一次视觉上就是被点亮了一下。用hit属性配合边框变色让这一层响应了看得更清楚。运行验证Qt Creator 打开示例的CMakeLists.txt按CtrlR运行保持拦截开关关闭点最内层获得焦点再按任意键观察内→中→外三层依次闪烁蓝→橙→红底部文字提示按键逐层传播打开拦截开关再按键看只有最内层闪蓝、中外层不再响应底部文字提示拦截开启。小结想让某个面板对某些快捷键整个包一层统一处理可在父级写Keys.onPressed统一收口。对话框这类屏蔽底层交互的界面常靠在上层拦截按键事件实现模态。需要区分快捷键归按钮、普通输入归输入框时用accepted决定谁先谁后。已验证环境Qt 版本Qt 6.11.1操作系统Windows 11项目地址QML 示例合集V2.0 - qml_keys_focus
返回列表