这篇文章围绕 QML 里最常见的三种选择控件展开:Switch、CheckBox 和 RadioButton。它们几乎覆盖了日常开发里所有"勾选 / 取消"类的交互场景。
- Switch --- 二态切换,适合设置项启用/禁用
- CheckBox --- 多选框,支持父子联动
- RadioButton --- 单选按钮,同一容器内自动互斥
Switch - 开关按钮
两个设置项的开关,分别控制 Wi-Fi 和 Bluetooth 状态。

演示代码
qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
FadeInAnimation {
property bool wifiEnabled: true
property bool bluetoothEnabled: false
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
GridLayout {
Layout.alignment: Qt.AlignHCenter
columns: 2
rowSpacing: 12
columnSpacing: 12
Text { text: qsTr("Wi-Fi:") }
Switch {
checked: wifiEnabled
onClicked: wifiEnabled = !wifiEnabled
}
Text { text: qsTr("Bluetooth:") }
Switch {
checked: bluetoothEnabled
onClicked: bluetoothEnabled = !bluetoothEnabled
}
}
Item { Layout.fillHeight: true }
}
}
关键逻辑解析
Switch 本身带有 checked 属性,但示例里没有直接让它自己管自己,而是把状态提到了外部:
checked: wifiEnabled只做显示绑定,Switch 的开/关状态永远跟随wifiEnabled。onClicked: wifiEnabled = !wifiEnabled在点击时把外部状态翻一次,UI 随后自动刷新。
这样做的好处是状态与 UI 解耦。后续如果你想换成网络请求返回值、配置文件读取,或者加一组音效/震动反馈,都只需要改 wifiEnabled 的赋值位置,而不用动 Switch 本身的绑定。相比直接监听 onCheckedChanged,显式在 onClicked 里切换更符合"用户主动触发"的语义,也不容易在程序初始化时被误触发。
适用场景:设置页、功能开关、偏好配置。
CheckBox - 复选框
一个父节点和两个孩子节点,父子选中状态自动联动。

演示代码
qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
FadeInAnimation {
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
ButtonGroup {
id: childGroup
exclusive: false
checkState: parentBox.checkState
buttons: childColumn.children
}
CheckBox {
id: parentBox
text: "父节点"
checkState: childGroup.checkState
Layout.alignment: Qt.AlignLeft
}
Column {
id: childColumn
Layout.leftMargin: 15
Layout.alignment: Qt.AlignLeft
spacing: 15
CheckBox { text: "子节点 1" }
CheckBox { text: "子节点 2" }
}
Item { Layout.fillHeight: true }
}
}
关键逻辑解析
父子联动的核心是两组 checkState 的互相引用,Qt 会帮你处理中间状态:
ButtonGroup把两个子 CheckBox 归为一组。exclusive: false表示它不是单选组,子选项可以多选。buttons: childColumn.children是批量绑定写法,只要放在childColumn里的 CheckBox 都会自动加入分组,省得一个个写ButtonGroup.group。parentBox.checkState: childGroup.checkState让父框跟随子框:全选为勾选,全不选为未勾选,部分选中就是半选状态。- 反过来
childGroup.checkState: parentBox.checkState让点击父框时,子框会一次性全部选中或取消。
所以这两行看起来是双向绑定,其实是 Qt 的 checkState 在内部做了收敛:子框状态决定父框显示,父框点击又会同步所有子框。写权限树、分类筛选时直接套这个结构,基本不用再手写递归。
适用场景:权限选择、分类筛选、树形多选。
RadioButton - 单选按钮
两组单选按钮,展示"同一容器内互斥"和"跨容器不互斥"的区别。

演示代码
qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
FadeInAnimation {
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
RadioButton {
text: "在布局外-单选按钮"
Layout.alignment: Qt.AlignLeft
onClicked: {
textResult.text = "在同一布局内的按钮才能进行单选";
}
}
Column {
id: childColumn
Layout.leftMargin: 15
Layout.alignment: Qt.AlignLeft
spacing: 15
RadioButton {
text: "在布局内-单选按钮1"
onClicked: {
textResult.text = "点击了" + text;
}
}
RadioButton {
text: "在布局内-单选按钮2"
onClicked: {
textResult.text = "点击了" + text;
}
}
}
Text {
id: textResult
Layout.alignment: Qt.AlignLeft
}
Item { Layout.fillHeight: true }
}
}
关键逻辑解析
RadioButton 的互斥逻辑比想象中省事,但也有容易踩坑的地方:
- 同一个父容器里的 RadioButton 会自动互斥,这是默认行为,不需要额外写
ButtonGroup。示例中"布局内-单选按钮 1"和"布局内-单选按钮 2"都在childColumn里,所以点一个会自动取消另一个。 - "布局外-单选按钮"放在
ColumnLayout根层级,和childColumn不是同一个直接父容器,因此它和里面的两个 RadioButton 互不干扰。 - 实际布局中经常遇到单选按钮分散在不同 Row、Column 或自定义组件里的情况。这时候需要显式建一个
ButtonGroup,把buttons指向这些跨容器的 RadioButton,才能强制它们互斥。
另外,RadioButton 的 autoExclusive 默认是 true。如果你某个特殊场景下不想互斥,把它设成 false 即可,不过这种情况比较少见。
适用场景:性别选择、支付方式、单选配置项。
对比表格
| 控件 | 状态数 | 是否互斥 | 关键机制 | 典型场景 |
|---|---|---|---|---|
| Switch | 2 | 否 | checked + 取反 |
设置开关 |
| CheckBox | 3 | 否 | ButtonGroup + checkState |
多选、父子联动 |
| RadioButton | 2 | 同一容器内自动互斥 | 容器隐式分组 | 单选 |
运行验证
- Qt Creator 打开
qml_button/CMakeLists.txt; - 按
Ctrl+R运行; - 左侧导航切换到对应 demo,测试开关切换、复选框联动、单选互斥。
扩展复用方向
- 把 Switch 封装成设置项组件,统一标签、开关和分隔线。
- 用 CheckBox 做表格行选择,结合
ButtonGroup实现全选/反选。 - 用
ButtonGroup显式管理跨布局的 RadioButton,实现复杂表单分组。
已验证环境:
- Qt 版本:Qt 6.8.2、Qt 6.11.1
- 操作系统:Windows 11
- GitHub:QML-Minimal-Demos/qml_button