Qt Quick Controls 控件库、样式与布局(八)

适合人群: 已掌握 Qt Quick 基础视觉元素,想使用完整 UI 控件库的开发者

前言

上一篇我们用 RectangleTextMouseArea 手工搭建了 UI 组件。但在实际开发中,按钮、输入框、复选框、滑块这些常见控件不需要从零造------Qt Quick Controls 模块提供了一套完整的、开箱即用的 UI 控件库。

本文系统介绍 Qt Quick Controls 的核心控件、控件解剖结构、内置样式,以及如何用 Layouts 模块管理控件的排列与尺寸。


一、什么是 Qt Quick Controls

QtQuick.Controls 是建立在 Qt Quick 之上的控件模块,提供了:

  • 按钮类:ButtonCheckBoxRadioButtonSwitch
  • 输入类:TextFieldTextAreaSliderSpinBoxComboBox
  • 容器类:GroupBoxFrameScrollViewTabBar
  • 弹窗类:DialogPopupMenuDrawer
  • 导航类:StackViewSwipeViewPageIndicator
  • 显示类:LabelProgressBarBusyIndicator

导入方式:

arduino 复制代码
import QtQuick.Controls

二、控件的解剖结构

理解 Qt Quick Controls 的关键是理解每个控件由哪些部分组成。以 Button 为例:

css 复制代码
Button
├── background   ← 背景(Rectangle、图片等)
├── contentItem  ← 内容区域(通常是 Text 或 Icon)
├── indicator    ← 指示器(CheckBox 的勾选框等)
└── overlay      ← 覆盖层(按下时的涟漪效果等)

这个结构意味着你可以单独替换任意部分来自定义外观,而不需要重写整个控件:

less 复制代码
Button {
    text: "自定义按钮"

    // 只替换背景,保留其他默认行为
    background: Rectangle {
        radius: 8
        color: parent.pressed ? "#2C72C7" : "#4A90E2"
        border.width: 0
    }

    // 只替换文字样式
    contentItem: Text {
        text: parent.text
        font.pixelSize: 15
        font.bold: true
        color: "white"
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
    }
}

三、内置样式

Qt Quick Controls 提供了几套内置样式,无需任何代码即可改变所有控件的外观。

可用样式

样式名 特点
Basic 极简风格,白色背景,适合自定义的起点
Fusion 跨平台桌面风格,类 Qt Widgets 外观
Material Google Material Design 风格
Universal Windows Universal 风格
iOS Apple iOS 风格(需在 iOS 平台)
macOS macOS 原生风格
Windows Windows 原生风格

设置全局样式

方式一:在 main.cpp 中设置(推荐)

arduino 复制代码
#include <QQuickStyle>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQuickStyle::setStyle("Material");    // 设置为 Material 风格

    QQmlApplicationEngine engine;
    // ...
}

方式二:通过环境变量

ini 复制代码
QT_QUICK_CONTROLS_STYLE=Material ./MyApp

方式三:在 qtquickcontrols2.conf 配置文件中设置

在项目根目录创建 qtquickcontrols2.conf,并在 CMakeLists.txt 中注册为资源:

ini 复制代码
[Controls]
Style=Material

[Material]
Theme=Light
Accent=Blue
Primary=BlueGrey

Material 样式示例

less 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Material

ApplicationWindow {
    width: 360; height: 500
    visible: true

    // Material 样式全局配置
    Material.theme: Material.Light
    Material.accent: Material.Blue
    Material.primary: Material.BlueGrey

    Column {
        anchors.centerIn: parent
        spacing: 16
        width: 280

        Button {
            width: parent.width
            text: "普通按钮"
        }

        Button {
            width: parent.width
            text: "高亮按钮"
            highlighted: true    // Material 风格下显示强调色
        }

        Button {
            width: parent.width
            text: "扁平按钮"
            flat: true
        }
    }
}

四、按钮类控件

4.1 Button

arduino 复制代码
Button {
    text: "提交"
    enabled: true          // 是否可点击
    highlighted: false     // 强调样式(Material 风格有明显效果)
    flat: false            // 扁平样式(无边框背景)
    checkable: false       // 是否可切换选中状态
    icon.source: "images/send.png"    // 图标

    onClicked: console.log("提交")
    onPressAndHold: console.log("长按")
}

4.2 CheckBox --- 复选框

arduino 复制代码
Column {
    spacing: 8

    CheckBox {
        id: agreeCheck
        text: "我已阅读并同意用户协议"
        checked: false
        onCheckedChanged: console.log("同意状态:" + checked)
    }

    CheckBox {
        text: "订阅新闻邮件"
        checked: true
    }

    Button {
        text: "提交"
        enabled: agreeCheck.checked    // 绑定:勾选协议后才可提交
    }
}

4.3 RadioButton --- 单选框

同一 ButtonGroup 中的单选框互斥:

vbnet 复制代码
import QtQuick
import QtQuick.Controls

Column {
    spacing: 8

    Label {
        text: "选择性别:"
        font.bold: true
    }

    ButtonGroup {
        id: genderGroup
    }

    RadioButton {
        text: "男"
        ButtonGroup.group: genderGroup
        checked: true
    }

    RadioButton {
        text: "女"
        ButtonGroup.group: genderGroup
    }

    RadioButton {
        text: "不愿透露"
        ButtonGroup.group: genderGroup
    }

    Label {
        text: "已选:" + genderGroup.checkedButton?.text
        color: "#888"
        font.pixelSize: 13
    }
}

4.4 Switch --- 开关

arduino 复制代码
Column {
    spacing: 12

    Switch {
        id: wifiSwitch
        text: "Wi-Fi"
        checked: true
        onCheckedChanged: console.log("Wi-Fi:" + (checked ? "开" : "关"))
    }

    Switch {
        text: "蓝牙"
        checked: false
    }

    Switch {
        text: "深色模式"
        checked: false
    }
}

五、输入类控件

5.1 TextField --- 单行输入

less 复制代码
Column {
    spacing: 12
    width: 280

    TextField {
        id: emailField
        width: parent.width
        placeholderText: "邮箱地址"
        inputMethodHints: Qt.ImhEmailCharactersOnly    // 键盘类型提示
        validator: RegularExpressionValidator {
            regularExpression: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/
        }
    }

    TextField {
        width: parent.width
        placeholderText: "密码"
        echoMode: TextInput.Password    // 密码掩码
    }

    TextField {
        width: parent.width
        placeholderText: "手机号"
        inputMethodHints: Qt.ImhDigitsOnly    // 只允许数字键盘
        maximumLength: 11
    }
}

5.2 TextArea --- 多行输入

less 复制代码
ScrollView {
    width: 300
    height: 150

    TextArea {
        placeholderText: "请输入详细描述..."
        wrapMode: TextArea.Wrap
        font.pixelSize: 14
    }
}

5.3 Slider --- 滑块

yaml 复制代码
Column {
    spacing: 16
    width: 300

    // 水平滑块
    Row {
        spacing: 12
        Label {
            anchors.verticalCenter: parent.verticalCenter
            text: "音量"
            width: 40
        }
        Slider {
            id: volumeSlider
            width: 200
            from: 0; to: 100; value: 70
            stepSize: 1
        }
        Label {
            anchors.verticalCenter: parent.verticalCenter
            text: Math.round(volumeSlider.value)
            width: 30
        }
    }

    // 垂直滑块
    Slider {
        orientation: Qt.Vertical
        height: 120
        from: 0; to: 100; value: 50
    }
}

5.4 SpinBox --- 数字输入框

yaml 复制代码
Row {
    spacing: 12
    Label {
        anchors.verticalCenter: parent.verticalCenter
        text: "数量:"
    }
    SpinBox {
        from: 1
        to: 99
        value: 1
        stepSize: 1
        editable: true    // 允许直接键盘输入
    }
}

5.5 ComboBox --- 下拉选择框

arduino 复制代码
Column {
    spacing: 12
    width: 240

    ComboBox {
        width: parent.width
        model: ["北京", "上海", "广州", "深圳", "杭州"]
        onCurrentIndexChanged: console.log("选中:" + currentText)
    }

    // 可编辑的 ComboBox
    ComboBox {
        width: parent.width
        editable: true
        model: ListModel {
            ListElement { text: "苹果" }
            ListElement { text: "香蕉" }
            ListElement { text: "橙子" }
        }
        onAccepted: {
            if (find(editText) === -1)
                model.append({ text: editText })    // 添加新选项
        }
    }
}

六、Layouts 布局模块

QtQuick.Layouts 提供了比 anchors 更强大的布局管理,特别适合需要自适应尺寸的 UI。

导入:

arduino 复制代码
import QtQuick.Layouts

6.1 RowLayout --- 水平布局

css 复制代码
RowLayout {
    width: 400
    spacing: 8

    Button { text: "取消" }

    Item { Layout.fillWidth: true }    // 弹性空间,把后面的按钮推到右边

    Button { text: "确认"; highlighted: true }
}

6.2 ColumnLayout --- 垂直布局

yaml 复制代码
ColumnLayout {
    width: 300
    spacing: 12

    Label { text: "用户名" }

    TextField {
        Layout.fillWidth: true    // 填满父布局宽度
        placeholderText: "请输入用户名"
    }

    Label { text: "密码" }

    TextField {
        Layout.fillWidth: true
        echoMode: TextInput.Password
        placeholderText: "请输入密码"
    }

    Button {
        Layout.fillWidth: true
        Layout.topMargin: 8
        text: "登录"
        highlighted: true
    }
}

6.3 GridLayout --- 网格布局

css 复制代码
GridLayout {
    columns: 2
    columnSpacing: 12
    rowSpacing: 12
    width: 320

    Label { text: "姓名:" }
    TextField { Layout.fillWidth: true; placeholderText: "请输入姓名" }

    Label { text: "手机:" }
    TextField { Layout.fillWidth: true; placeholderText: "请输入手机号" }

    Label { text: "城市:" }
    ComboBox {
        Layout.fillWidth: true
        model: ["北京", "上海", "广州"]
    }

    // 跨列的按钮
    Button {
        Layout.columnSpan: 2
        Layout.fillWidth: true
        text: "提交"
        highlighted: true
    }
}

6.4 Layout 附加属性

在子元素上使用 Layout.* 属性控制其在布局中的行为:

arduino 复制代码
RowLayout {
    width: 400

    Button {
        text: "固定宽度"
        Layout.preferredWidth: 100    // 期望宽度
        Layout.minimumWidth: 80       // 最小宽度
        Layout.maximumWidth: 120      // 最大宽度
    }

    TextField {
        Layout.fillWidth: true        // 填满剩余空间
        Layout.preferredHeight: 40
    }

    Button {
        text: "搜索"
        Layout.alignment: Qt.AlignVCenter    // 垂直居中对齐
    }
}

七、综合示例:用户注册表单

整合本文所有知识点,构建一个完整的注册表单:

css 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

ApplicationWindow {
    width: 400
    height: 580
    visible: true
    title: "用户注册"

    // 整体滚动支持
    ScrollView {
        anchors.fill: parent
        contentWidth: availableWidth

        ColumnLayout {
            width: parent.width
            spacing: 0

            // 顶部标题区
            Rectangle {
                Layout.fillWidth: true
                height: 100
                color: "#4A90E2"

                Column {
                    anchors.centerIn: parent
                    spacing: 4

                    Text {
                        anchors.horizontalCenter: parent.horizontalCenter
                        text: "创建账号"
                        font.pixelSize: 22
                        font.bold: true
                        color: "white"
                    }

                    Text {
                        anchors.horizontalCenter: parent.horizontalCenter
                        text: "加入我们,开始你的旅程"
                        font.pixelSize: 13
                        color: "#d0e8ff"
                    }
                }
            }

            // 表单区域
            ColumnLayout {
                Layout.fillWidth: true
                Layout.margins: 24
                spacing: 16

                // 姓名行
                RowLayout {
                    Layout.fillWidth: true
                    spacing: 12

                    ColumnLayout {
                        Layout.fillWidth: true
                        spacing: 4
                        Label { text: "姓"; font.pixelSize: 13; color: "#555" }
                        TextField {
                            Layout.fillWidth: true
                            placeholderText: "姓氏"
                        }
                    }

                    ColumnLayout {
                        Layout.fillWidth: true
                        spacing: 4
                        Label { text: "名"; font.pixelSize: 13; color: "#555" }
                        TextField {
                            Layout.fillWidth: true
                            placeholderText: "名字"
                        }
                    }
                }

                // 邮箱
                ColumnLayout {
                    Layout.fillWidth: true
                    spacing: 4
                    Label { text: "邮箱地址"; font.pixelSize: 13; color: "#555" }
                    TextField {
                        id: emailField
                        Layout.fillWidth: true
                        placeholderText: "example@email.com"
                        inputMethodHints: Qt.ImhEmailCharactersOnly
                    }
                }

                // 密码
                ColumnLayout {
                    Layout.fillWidth: true
                    spacing: 4
                    Label { text: "密码"; font.pixelSize: 13; color: "#555" }
                    TextField {
                        id: passwordField
                        Layout.fillWidth: true
                        placeholderText: "至少 8 位字符"
                        echoMode: TextInput.Password
                    }
                    Label {
                        visible: passwordField.text.length > 0 && passwordField.text.length < 8
                        text: "密码长度不足 8 位"
                        font.pixelSize: 12
                        color: "#E24A4A"
                    }
                }

                // 城市选择
                ColumnLayout {
                    Layout.fillWidth: true
                    spacing: 4
                    Label { text: "所在城市"; font.pixelSize: 13; color: "#555" }
                    ComboBox {
                        Layout.fillWidth: true
                        model: ["请选择城市", "北京", "上海", "广州", "深圳", "杭州", "成都"]
                    }
                }

                // 性别选择
                ColumnLayout {
                    Layout.fillWidth: true
                    spacing: 4
                    Label { text: "性别"; font.pixelSize: 13; color: "#555" }
                    RowLayout {
                        ButtonGroup { id: genderGroup }
                        RadioButton {
                            text: "男"
                            ButtonGroup.group: genderGroup
                            checked: true
                        }
                        RadioButton {
                            text: "女"
                            ButtonGroup.group: genderGroup
                        }
                    }
                }

                // 接收通知
                CheckBox {
                    id: notifyCheck
                    text: "接收产品更新通知"
                    checked: true
                }

                // 同意协议
                CheckBox {
                    id: agreeCheck
                    text: "我已阅读并同意《用户协议》和《隐私政策》"
                    font.pixelSize: 13
                }

                // 注册按钮
                Button {
                    Layout.fillWidth: true
                    Layout.topMargin: 8
                    text: "立即注册"
                    highlighted: true
                    enabled: agreeCheck.checked &&
                             emailField.text.length > 0 &&
                             passwordField.text.length >= 8
                    onClicked: console.log("注册成功!邮箱:" + emailField.text)
                }

                // 登录跳转
                RowLayout {
                    Layout.alignment: Qt.AlignHCenter
                    Label {
                        text: "已有账号?"
                        font.pixelSize: 13
                        color: "#888"
                    }
                    Button {
                        text: "立即登录"
                        flat: true
                        font.pixelSize: 13
                        onClicked: console.log("跳转到登录页")
                    }
                }
            }
        }
    }
}

八、常见问题

Q:Layout.fillWidthanchors.fill 有什么区别?

  • anchors.fill:用于 anchors 定位系统,将元素尺寸绑定到父元素
  • Layout.fillWidth:用于 Layouts 布局系统,让元素占满布局中的剩余宽度

两套系统不能混用------在 RowLayout / ColumnLayout 的直接子元素上,用 Layout.* 属性,不要用 anchors(会产生冲突警告)。

Q:控件的默认尺寸从哪里来?

Qt Quick Controls 的每个控件都有 implicitWidthimplicitHeight,由控件内容自动计算。不设置 width/height 时控件使用 implicit 尺寸;放入 Layout 后可以用 Layout.fillWidth 覆盖。

Q:如何统一修改应用内所有按钮的字体大小?

使用 ApplicationWindow 上的 font 属性设置全局字体,所有控件会继承:

css 复制代码
ApplicationWindow {
    font.pixelSize: 15
    font.family: "PingFang SC"
    // ...
}

总结

控件 / 概念 用途
Button 可点击按钮,支持图标、高亮、扁平样式
CheckBox 复选框,独立勾选状态
RadioButton 单选框,配合 ButtonGroup 实现互斥
Switch 开关控件,适合设置页面
TextField 单行输入,支持验证器和键盘类型提示
TextArea 多行输入,配合 ScrollView 使用
Slider 滑块,水平或垂直方向
SpinBox 数字步进框
ComboBox 下拉选择,支持可编辑模式
RowLayout 水平自适应布局
ColumnLayout 垂直自适应布局
GridLayout 网格布局,支持跨行跨列
Layout.* 附加属性 控制子元素在布局中的尺寸和对齐
内置样式 BasicMaterialFusion 等,全局切换控件外观
相关推荐
兔司基2 小时前
Node.js/Express 实现 AI 流式输出 (SSE) 踩的坑:为什么客户端会“瞬间断开连接”?
前端
yuki_uix2 小时前
一次 CR 引发的思考:我的 rules.ts 构想,究竟属于哪种开发哲学?
前端·ai编程
可视之道2 小时前
前端大屏适配方案:rem、vw/vh、scale 到底选哪个?
前端
小码哥_常2 小时前
别在Android ViewModel里处理异常啦,真的很坑!
前端
05大叔2 小时前
RAG开发
java·服务器·前端
console.log('npc')2 小时前
在 React 中,useRef、ref 属性以及 forwardRef 是处理“引用”(访问 DOM 节点或组件实例)的核心概念
前端·react.js·前端框架
小小小小宇2 小时前
语法全景对照
前端
weixin_704266052 小时前
Spring Boot 入门了解
前端·firefox
冲浪中台2 小时前
如何实现低代码源码级交付和私有化部署
前端·低代码·私有化部署·源代码管理