这篇介绍两个 demo:
- 事件传递 --- 当多个鼠标区域叠加时,控制点击事件要不要传给下层
- 自定义按钮 --- 用基础组件做一个带交互动效的按钮
事件传递

当两个 MouseArea 叠在一起(比如按钮盖在背景上),你点在上层,这个点击到底是"上层收到"还是"两层都收到"?默认情况下只给最上层。但有些场景希望点击能"穿过"上层传给下面,QML 提供了开关来控制。这个 demo 用一个开关现场演示开/关两种行为的差别。
演示代码
qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
FadeInAnimation {
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
TitleSeparator {
title: "事件传递"
description: "演示 propagateComposedEvents 与 mouse.accepted 控制事件在重叠 MouseArea 间的传递"
}
Rectangle {
id: parentBox
Layout.fillWidth: true
Layout.fillHeight: true
radius: 8
color: "#EAF4FF"
border.color: "#1296FF"
border.width: 2
property int parentCount: 0
// 外层 MouseArea 事件日志
Text {
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 12
text: "外层 MouseArea\n点击次数: " + parentBox.parentCount
font.pointSize: 12
color: "#1296FF"
horizontalAlignment: Text.AlignHCenter
}
MouseArea {
id: parentMouse
anchors.fill: parent
onClicked: (mouse) => {
parentBox.parentCount++
}
}
// 内层方块(z 更高,覆盖在父 MouseArea 之上)
Rectangle {
id: childBox
width: 180
height: 80
radius: 8
anchors.centerIn: parent
color: childMouse.pressed ? "#FF6B6B" : (childMouse.containsMouse ? "#FF8787" : "#FFD4D4")
border.color: "#FF6B6B"
border.width: 2
z: 1
property int childCount: 0
Text {
anchors.centerIn: parent
text: "内层 MouseArea\n点击次数: " + childBox.childCount
font.pointSize: 12
color: "#C0392B"
horizontalAlignment: Text.AlignHCenter
}
MouseArea {
id: childMouse
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: propagateSwitch.checked
onClicked: (mouse) => {
childBox.childCount++
// 若 propagate 为 true,放行事件给下层
if (propagateComposedEvents) {
mouse.accepted = false
}
}
}
}
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 20
Switch {
id: propagateSwitch
text: "放行事件给下层"
checked: false
}
Button {
text: "重置计数"
onClicked: {
parentBox.parentCount = 0
childBox.childCount = 0
}
}
}
}
}
关键逻辑解析
两层的叠加关系
最外层是一个铺满内容区的 MouseArea(parentMouse),中间盖着一个方块 childBox,方块里又有自己的 MouseArea(childMouse)。因为 childBox 设了 z: 1,它比外层高,所以点中间那个方块时,事件首先落在内层 childMouse 上,而不直接掉到外层。
默认行为:点到哪层算哪层
先看开关默认处于"关"的状态。这时 propagateComposedEvents 是 false,点内层方块,只有内层计数加一,外层纹丝不动。这就是 QML 的默认:事件被最上层 MouseArea "吃掉"了,不会再往下传。很多场景下这是我们要的行为------比如点一个按钮,不希望它还点到了按钮后面的背景。
打开开关:让事件穿下去
把"放行事件给下层"打开,propagateComposedEvents 变 true。但光开这一个还不够,因为开了它只代表"允许传递",真正把事件放行还得在 onClicked 里把这件事件标记为"未接受":
qml
mouse.accepted = false
mouse.accepted 就是"这事件我接不接"。设为 false 表示"我不收这件事件",事件就会继续往下层走,外层 parentMouse 也能收到,于是内层、外层计数一起加。这串组合起来的效果是:内层自己处理了(自己的计数照加),同时把事件漏给了下层。
开关的两态对比
一个开关、一次点击,就能直观地对比出两套行为------开的时候点中间,两层都计数;关的时候点中间,只有内层计数。理解这个对排查"点击没反应"类问题很有用:很多时候是它被某个上层 MouseArea 悄悄拦截了。
自定义按钮

在有些场景下需要特殊定制的按钮,现成的 Button 不容易满足,不如用最基础的元素造一个。原理很简单:一个 Rectangle 当外观,一个 MouseArea 铺上去接收鼠标,再用鼠标状态去驱动外观变化。
演示代码
qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
FadeInAnimation {
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
TitleSeparator {
title: "自定义按钮"
description: "使用 MouseArea 实现完整的按钮交互:悬停变色、按下缩放、释放回弹"
}
// 自定义按钮
Rectangle {
id: btn
Layout.alignment: Qt.AlignHCenter
width: 140
height: 50
radius: 8
color: mouseArea.pressed ? "#0E7BD9" : (mouseArea.containsMouse ? "#3FA9F5" : "#1296FF")
scale: mouseArea.pressed ? 0.95 : 1.0
Behavior on scale {
NumberAnimation { duration: 100; easing.type: Easing.OutQuad }
}
Behavior on color {
ColorAnimation { duration: 150 }
}
Text {
anchors.centerIn: parent
text: "自定义按钮"
color: "white"
font.pointSize: 13
font.bold: true
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: btn.clickCount++
}
property int clickCount: 0
}
Text {
Layout.alignment: Qt.AlignHCenter
text: "矩形按钮点击: " + btn.clickCount + " 次"
font.pointSize: 12
color: "#999"
}
// 圆角图标按钮
Rectangle {
id: iconBtn
Layout.alignment: Qt.AlignHCenter
width: 50
height: 50
radius: 25
color: iconMouse.pressed ? "#E8910C" : (iconMouse.containsMouse ? "#FFB74D" : "#FF9800")
scale: iconMouse.pressed ? 0.9 : 1.0
Behavior on scale {
NumberAnimation { duration: 100; easing.type: Easing.OutQuad }
}
Behavior on color {
ColorAnimation { duration: 150 }
}
Text {
anchors.centerIn: parent
text: "+"
color: "white"
font.pointSize: 24
font.bold: true
}
MouseArea {
id: iconMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: iconBtn.iconClickCount++
}
property int iconClickCount: 0
}
Text {
Layout.alignment: Qt.AlignHCenter
text: "圆形按钮点击: " + iconBtn.iconClickCount + " 次"
font.pointSize: 12
color: "#999"
}
Item { Layout.fillHeight: true }
}
}
关键逻辑解析
一个按钮的三态外观
自定义按钮的中枢是把鼠标状态映射到颜色上。以蓝色矩形按钮为例:
qml
color: mouseArea.pressed ? "#0E7BD9" : (mouseArea.containsMouse ? "#3FA9F5" : "#1296FF")
三种状态:普通、悬停、按下各对应一个颜色,一眼就能看出鼠标所处的阶段。
按下缩小、松开回弹
除了变色,还让按钮在按下时轻微缩一点:
qml
scale: mouseArea.pressed ? 0.95 : 1.0
按住时 scale 降到 0.95(比原尺寸小一点),松开回到 1.0。配合 100 毫秒的 Behavior 过渡,按下、弹起的动作就带了一点"按下感",比单纯变色更生动。
鼠标变成手型
cursorShape: Qt.PointingHandCursor 让鼠标移入按钮时从箭头变成手型,这是所有可点击控件都该有的细节,用户一看就知道"这里能点"。它同样依赖 hoverEnabled: true 才能跟随悬停生效。
Behavior 统一做平滑
无论状态怎么在三种颜色、两种尺寸之间切换,都是动画过渡而不是生硬跳变。
运行验证
- Qt Creator 打开示例的
CMakeLists.txt; - 按
Ctrl+R运行。
扩展复用方向
- 事件传递的开关思路可以做"半透明遮罩不挡点击"------遮罩开着但
accepted设为 false,点击穿透 - 自定义按钮套模板改外形就能出很多种按钮:指针在圆角矩形上变胶囊、图标按钮、Toggle 开关按钮
- 给按钮加
Behavior的这套写法可以扩展到任意可点击元素上,统一手感 - 把自定义按钮封装成可复用组件,暴露
pressed、hovered等信号,全项目统一用
已验证环境:
- Qt 版本:Qt 6.8、Qt 6.11
- 操作系统:Windows 11
- GitHub:QML-Minimal-Demos/qml_mousearea