Qt Quick Controls 全览控件、弹窗、导航与样式定制(十一)

适合人群: 已掌握基础 QML 语法,想系统掌握完整控件库的开发者 > 预计耗时: 90 分钟


前言

Qt Quick Controls 提供了构建完整应用界面所需的全套控件------从最基础的按钮、输入框,到菜单、抽屉、页面导航。本文系统梳理每一类控件的完整用法,并深入讲解样式系统和自定义控件外观。


一、控件分类总览

Qt Quick Controls 的控件按功能分为六大类:

css 复制代码
QtQuick.Controls
├── 按钮类     Button · CheckBox · RadioButton · Switch · RoundButton
├── 输入类     TextField · TextArea · Slider · Dial · SpinBox · ComboBox · Tumbler
├── 显示类     Label · ProgressBar · BusyIndicator · DelayButton
├── 容器类     Frame · GroupBox · ScrollView · Pane · Page · TabBar · ToolBar
├── 弹窗类     Dialog · Drawer · Menu · Popup · ToolTip
└── 导航类     StackView · SwipeView · PageIndicator

二、内置样式一览

Qt Quick Controls 内置多套样式,一行代码即可切换全局外观。

Basic 样式(默认,跨平台)

图片来源:Qt 官方文档 --- Basic Style

轻量极简,性能最佳,适合作为自定义样式的起点。

Material 样式(Google Material Design)

图片来源:Qt 官方文档 --- Material Style

适合移动端和现代桌面应用,视觉效果丰富。

Fusion 样式(桌面风格)

传统桌面应用外观,与 Qt Widgets 视觉语言一致,适合企业桌面工具。

各平台默认样式

操作系统 默认样式
Android Material
iOS iOS Style
macOS macOS Style
Windows Windows Style
Linux / 其他 Fusion

设置样式的三种方式

方式一:编译时导入(推荐,性能最优)

arduino 复制代码
// 必须在所有其他 QtQuick.Controls 导入之前
import QtQuick.Controls.Material

ApplicationWindow {
    Material.theme: Material.Light
    Material.accent: Material.Blue
}

方式二:运行时 C++ 设置

arduino 复制代码
#include <QQuickStyle>
QQuickStyle::setStyle("Material");

方式三:配置文件 qtquickcontrols2.conf

ini 复制代码
[Controls]
Style=Material

[Material]
Theme=Light
Accent=Blue

三、按钮类控件完整用法

3.1 Button 的状态属性

arduino 复制代码
Button {
    text: "操作按钮"

    // 核心状态属性(只读,反映当前交互状态)
    // pressed    --- 正在按下
    // hovered    --- 鼠标悬停
    // checked    --- 已选中(checkable 时有效)
    // enabled    --- 是否可用
    // highlighted --- 强调样式(Material 下显示 accent 色)
    // flat       --- 扁平样式(无背景边框)

    highlighted: true
    flat: false
    checkable: true     // 允许切换选中状态
    icon.source: "images/send.svg"
    icon.width: 18
    icon.height: 18
}

3.2 DelayButton --- 长按确认按钮

需要长按才能触发,适合危险操作(删除、格式化):

arduino 复制代码
DelayButton {
    text: "长按删除"
    delay: 1500     // 需要按住 1.5 秒

    onActivated: console.log("确认删除!")

    // 进度条自动显示按压进度
}

3.3 RoundButton --- 圆形按钮

arduino 复制代码
RoundButton {
    text: "+"
    font.pixelSize: 20
    highlighted: true

    // 或使用图标
    icon.source: "images/add.svg"
}

四、输入类控件完整用法

4.1 Dial --- 旋钮控件

适合音量、亮度等环形调节:

yaml 复制代码
import QtQuick
import QtQuick.Controls

Column {
    spacing: 8

    Dial {
        id: volumeDial
        from: 0; to: 100; value: 50
        stepSize: 1

        // 旋转模式
        inputMode: Dial.Circular      // 圆形拖动(默认)
        // inputMode: Dial.Horizontal // 水平拖动
        // inputMode: Dial.Vertical   // 垂直拖动
    }

    Label {
        anchors.horizontalCenter: parent.horizontalCenter
        text: "音量:" + Math.round(volumeDial.value)
    }
}

4.2 Tumbler --- 滚筒选择器

适合时间、日期选择:

yaml 复制代码
Row {
    spacing: 0

    Tumbler {
        id: hourTumbler
        model: 24
        delegate: Label {
            required property int index
            text: index.toString().padStart(2, "0")
            horizontalAlignment: Text.AlignHCenter
            verticalAlignment: Text.AlignVCenter
            opacity: 1.0 - Math.abs(Tumbler.displacement) / (Tumbler.tumbler.visibleItemCount / 2)
            font.pixelSize: Math.abs(Tumbler.displacement) < 0.5 ? 18 : 14
        }
    }

    Label {
        anchors.verticalCenter: parent.verticalCenter
        text: ":"
        font.pixelSize: 18
        font.bold: true
    }

    Tumbler {
        id: minuteTumbler
        model: 60
        delegate: Label {
            required property int index
            text: index.toString().padStart(2, "0")
            horizontalAlignment: Text.AlignHCenter
            verticalAlignment: Text.AlignVCenter
            opacity: 1.0 - Math.abs(Tumbler.displacement) / (Tumbler.tumbler.visibleItemCount / 2)
            font.pixelSize: Math.abs(Tumbler.displacement) < 0.5 ? 18 : 14
        }
    }
}

五、容器类控件

5.1 Frame 与 GroupBox

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

Column {
    spacing: 16
    width: 300

    // Frame:带边框的容器
    Frame {
        width: parent.width
        ColumnLayout {
            width: parent.width
            Label { text: "账号信息"; font.bold: true }
            TextField { Layout.fillWidth: true; placeholderText: "用户名" }
            TextField { Layout.fillWidth: true; placeholderText: "邮箱" }
        }
    }

    // GroupBox:带标题的 Frame
    GroupBox {
        width: parent.width
        title: "通知设置"
        ColumnLayout {
            width: parent.width
            CheckBox { text: "邮件通知" }
            CheckBox { text: "短信通知" }
            CheckBox { text: "推送通知"; checked: true }
        }
    }
}

5.2 TabBar + StackLayout --- 选项卡导航

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

Column {
    width: 400

    TabBar {
        id: tabBar
        width: parent.width

        TabButton { text: "首页" }
        TabButton { text: "发现" }
        TabButton { text: "消息" }
        TabButton { text: "我的" }
    }

    StackLayout {
        width: parent.width
        height: 300
        currentIndex: tabBar.currentIndex    // 与 TabBar 绑定

        Rectangle { color: "#E6F1FB"; Label { anchors.centerIn: parent; text: "首页内容" } }
        Rectangle { color: "#E1F5EE"; Label { anchors.centerIn: parent; text: "发现内容" } }
        Rectangle { color: "#FAEEDA"; Label { anchors.centerIn: parent; text: "消息内容" } }
        Rectangle { color: "#FAECE7"; Label { anchors.centerIn: parent; text: "我的内容" } }
    }
}

5.3 ToolBar --- 工具栏

css 复制代码
ApplicationWindow {
    width: 500; height: 400
    visible: true

    header: ToolBar {
        RowLayout {
            anchors.fill: parent

            ToolButton {
                icon.source: "images/menu.svg"
                onClicked: drawer.open()
            }

            Label {
                text: "应用标题"
                font.pixelSize: 16
                font.bold: true
                Layout.fillWidth: true
                horizontalAlignment: Text.AlignHCenter
            }

            ToolButton {
                icon.source: "images/search.svg"
            }

            ToolButton {
                icon.source: "images/more.svg"
            }
        }
    }
}

六、弹窗类控件

6.1 Dialog --- 标准对话框

yaml 复制代码
Dialog {
    id: confirmDialog
    anchors.centerIn: parent
    title: "确认操作"
    modal: true
    width: 280

    // 内容区
    contentItem: Label {
        text: "确定要删除这条记录吗?此操作不可撤销。"
        wrapMode: Text.Wrap
        padding: 8
    }

    // 标准按钮
    standardButtons: Dialog.Ok | Dialog.Cancel

    onAccepted: console.log("用户点击了确定")
    onRejected: console.log("用户取消了操作")
}

Button {
    text: "删除"
    onClicked: confirmDialog.open()
}

6.2 自定义 Dialog 内容

yaml 复制代码
Dialog {
    id: inputDialog
    anchors.centerIn: parent
    title: "重命名"
    modal: true
    width: 300

    contentItem: ColumnLayout {
        spacing: 12
        width: parent.width

        Label { text: "请输入新名称:" }

        TextField {
            id: nameField
            Layout.fillWidth: true
            placeholderText: "名称"
            focus: true    // 对话框打开时自动聚焦
        }
    }

    footer: DialogButtonBox {
        Button {
            text: "取消"
            DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
        }
        Button {
            text: "确认"
            enabled: nameField.text.length > 0
            highlighted: true
            DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
        }
    }

    onAccepted: console.log("新名称:" + nameField.text)
}

6.3 Drawer --- 侧滑抽屉

yaml 复制代码
ApplicationWindow {
    id: window
    width: 400; height: 600
    visible: true

    Drawer {
        id: drawer
        width: 260
        height: window.height
        edge: Qt.LeftEdge    // Qt.RightEdge / Qt.TopEdge / Qt.BottomEdge

        // 抽屉内容
        ColumnLayout {
            anchors.fill: parent
            anchors.margins: 0
            spacing: 0

            // 用户信息头部
            Rectangle {
                Layout.fillWidth: true
                height: 120
                color: "#4A90E2"

                Column {
                    anchors.centerIn: parent
                    spacing: 6
                    Rectangle {
                        width: 56; height: 56; radius: 28
                        color: "white"
                        anchors.horizontalCenter: parent.horizontalCenter
                        Label {
                            anchors.centerIn: parent
                            text: "用"
                            font.pixelSize: 22
                            font.bold: true
                            color: "#4A90E2"
                        }
                    }
                    Label {
                        text: "用户名"
                        color: "white"
                        font.pixelSize: 14
                        anchors.horizontalCenter: parent.horizontalCenter
                    }
                }
            }

            // 菜单列表
            Repeater {
                model: ["首页", "收藏", "历史记录", "设置", "帮助"]
                delegate: ItemDelegate {
                    required property string modelData
                    Layout.fillWidth: true
                    text: modelData
                    onClicked: {
                        console.log("导航到:" + modelData)
                        drawer.close()
                    }
                }
            }

            Item { Layout.fillHeight: true }

            ItemDelegate {
                Layout.fillWidth: true
                text: "退出登录"
            }
        }
    }

    Button {
        text: "打开抽屉"
        anchors.centerIn: parent
        onClicked: drawer.open()
    }
}
arduino 复制代码
Menu {
    id: contextMenu

    MenuItem {
        text: "复制"
        shortcut: "Ctrl+C"
        onTriggered: console.log("复制")
    }

    MenuItem {
        text: "粘贴"
        shortcut: "Ctrl+V"
        onTriggered: console.log("粘贴")
    }

    MenuSeparator {}    // 分割线

    Menu {
        title: "导出为"
        MenuItem { text: "PDF" }
        MenuItem { text: "PNG" }
        MenuItem { text: "SVG" }
    }

    MenuSeparator {}

    MenuItem {
        text: "删除"
        enabled: false    // 禁用状态
    }
}

// 右键触发
MouseArea {
    anchors.fill: parent
    acceptedButtons: Qt.RightButton
    onClicked: contextMenu.popup()    // 在鼠标位置弹出
}

6.5 ToolTip --- 悬停提示

less 复制代码
Button {
    text: "保存"
    icon.source: "images/save.svg"

    // 方式一:附加属性(最简单)
    ToolTip.visible: hovered
    ToolTip.text: "保存文件 (Ctrl+S)"
    ToolTip.delay: 800    // 悬停 800ms 后显示

    // 方式二:独立 ToolTip 组件(可自定义外观)
}

七、导航类控件

7.1 StackView --- 页面栈导航

StackView 实现类似移动端的前进/后退页面导航:

css 复制代码
// 页面切换时间线:
// push()  → 新页面从右侧滑入
// pop()   → 当前页面向右滑出
// replace() → 替换当前页面(无返回)

StackView {
    id: stackView
    anchors.fill: parent

    // 初始页面
    initialItem: homePage
}

Component {
    id: homePage
    Rectangle {
        color: "#f5f5f5"
        Column {
            anchors.centerIn: parent
            spacing: 12

            Label { text: "首页"; font.pixelSize: 24; font.bold: true }

            Button {
                text: "进入详情页"
                onClicked: stackView.push(detailPage, { title: "详情内容" })
            }
        }
    }
}

Component {
    id: detailPage
    Rectangle {
        property string title: ""
        color: "#E6F1FB"
        Column {
            anchors.centerIn: parent
            spacing: 12

            Label { text: title; font.pixelSize: 20 }

            Button {
                text: "← 返回"
                onClicked: stackView.pop()
            }
        }
    }
}

StackView 页面切换动画流程:

scss 复制代码
┌─────────────┐  push()   ┌─────────────┬─────────────┐
│   首页      │ ────────▶ │   首页      │   详情页    │
│  (当前)   │           │  (历史)   │  (当前)   │
└─────────────┘           └─────────────┴─────────────┘

                 pop()    ┌─────────────┐
                ────────▶ │   首页      │
                          │  (当前)   │
                          └─────────────┘

7.2 SwipeView + PageIndicator --- 横划导航

适合引导页、图片轮播、多步骤表单:

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

Column {
    width: 360
    spacing: 0

    SwipeView {
        id: swipeView
        width: parent.width
        height: 280

        // 第一页
        Rectangle {
            color: "#4A90E2"
            Label {
                anchors.centerIn: parent
                text: "欢迎使用"
                font.pixelSize: 24
                font.bold: true
                color: "white"
            }
        }

        // 第二页
        Rectangle {
            color: "#1D9E75"
            Label {
                anchors.centerIn: parent
                text: "功能介绍"
                font.pixelSize: 24
                font.bold: true
                color: "white"
            }
        }

        // 第三页
        Rectangle {
            color: "#E2934A"
            Label {
                anchors.centerIn: parent
                text: "开始使用"
                font.pixelSize: 24
                font.bold: true
                color: "white"
            }
        }
    }

    // 页面指示点
    PageIndicator {
        anchors.horizontalCenter: parent.horizontalCenter
        count: swipeView.count
        currentIndex: swipeView.currentIndex    // 双向绑定
        interactive: true    // 点击圆点可跳转
    }
}

八、自定义控件外观

8.1 替换 background 和 contentItem

每个控件的外观由 background(背景)和 contentItem(内容)组成,单独替换其中任意一个即可改变外观:

less 复制代码
// 自定义圆角按钮,保留所有交互行为
Button {
    id: btn
    text: "自定义按钮"
    width: 140; height: 44

    background: Rectangle {
        radius: btn.height / 2      // 完全圆角
        color: btn.pressed   ? "#2C72C7" :
               btn.hovered   ? "#5BA3E8" :
               btn.enabled   ? "#4A90E2" : "#AAAAAA"

        Behavior on color {
            ColorAnimation { duration: 120 }
        }

        border.width: 0
    }

    contentItem: Text {
        text: btn.text
        color: "white"
        font.pixelSize: 14
        font.bold: true
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
    }
}

8.2 自定义 ProgressBar

css 复制代码
ProgressBar {
    id: bar
    width: 300
    value: 0.65

    background: Rectangle {
        implicitWidth: 200; implicitHeight: 8
        color: "#e0e0e0"
        radius: 4
    }

    contentItem: Item {
        implicitWidth: 200; implicitHeight: 8

        Rectangle {
            width: bar.visualPosition * parent.width
            height: parent.height
            radius: 4

            // 渐变进度条
            gradient: Gradient {
                orientation: Gradient.Horizontal
                GradientStop { position: 0.0; color: "#4A90E2" }
                GradientStop { position: 1.0; color: "#1D9E75" }
            }

            Behavior on width {
                NumberAnimation { duration: 300; easing.type: Easing.OutCubic }
            }
        }
    }
}

8.3 封装统一风格的自定义控件

把自定义样式封装到独立的组件文件,在整个项目复用:

less 复制代码
// PrimaryButton.qml
import QtQuick
import QtQuick.Controls

Button {
    id: root
    height: 44

    property color primaryColor: "#4A90E2"

    background: Rectangle {
        radius: 8
        color: root.pressed ? Qt.darker(root.primaryColor, 1.2)
             : root.hovered ? Qt.lighter(root.primaryColor, 1.1)
             : root.enabled ? root.primaryColor
             :                "#cccccc"
        Behavior on color { ColorAnimation { duration: 100 } }
    }

    contentItem: Text {
        text: root.text
        color: root.enabled ? "white" : "#888888"
        font.pixelSize: 14
        font.bold: true
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
    }
}

使用:

css 复制代码
PrimaryButton { text: "确认"; width: 120 }
PrimaryButton { text: "危险操作"; width: 120; primaryColor: "#E24A4A" }
PrimaryButton { text: "成功"; width: 120; primaryColor: "#1D9E75" }

九、综合示例:设置页面

整合本文所有控件,构建一个完整的应用设置页面:

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

ApplicationWindow {
    width: 400; height: 650
    visible: true
    title: "设置"

    ScrollView {
        anchors.fill: parent
        contentWidth: availableWidth

        ColumnLayout {
            width: parent.width
            spacing: 0

            // 外观设置
            GroupBox {
                Layout.fillWidth: true
                Layout.margins: 16
                title: "外观"

                ColumnLayout {
                    width: parent.width
                    spacing: 4

                    RowLayout {
                        Layout.fillWidth: true
                        Label { text: "深色模式"; Layout.fillWidth: true }
                        Switch { id: darkSwitch }
                    }

                    RowLayout {
                        Layout.fillWidth: true
                        Label { text: "主题色"; Layout.fillWidth: true }
                        ComboBox {
                            model: ["蓝色", "绿色", "橙色", "紫色"]
                            Layout.preferredWidth: 100
                        }
                    }

                    RowLayout {
                        Layout.fillWidth: true
                        Label { text: "字体大小"; Layout.fillWidth: true }
                        Slider {
                            from: 12; to: 20; value: 15
                            stepSize: 1
                            Layout.preferredWidth: 120
                        }
                    }
                }
            }

            // 通知设置
            GroupBox {
                Layout.fillWidth: true
                Layout.leftMargin: 16
                Layout.rightMargin: 16
                title: "通知"

                ColumnLayout {
                    width: parent.width
                    spacing: 4

                    Repeater {
                        model: ["接收推送通知", "邮件提醒", "声音提示", "震动反馈"]
                        delegate: RowLayout {
                            required property string modelData
                            required property int index
                            Layout.fillWidth: true
                            Label { text: modelData; Layout.fillWidth: true }
                            Switch { checked: index < 2 }
                        }
                    }
                }
            }

            // 存储设置
            GroupBox {
                Layout.fillWidth: true
                Layout.leftMargin: 16
                Layout.rightMargin: 16
                title: "存储与数据"

                ColumnLayout {
                    width: parent.width
                    spacing: 8

                    Label {
                        text: "已用空间:1.2 GB / 5 GB"
                        font.pixelSize: 13; color: "#666"
                    }

                    ProgressBar {
                        Layout.fillWidth: true
                        value: 0.24
                    }

                    Button {
                        Layout.fillWidth: true
                        text: "清理缓存"
                        onClicked: cacheDialog.open()
                    }
                }
            }

            // 账号操作
            GroupBox {
                Layout.fillWidth: true
                Layout.leftMargin: 16
                Layout.rightMargin: 16
                Layout.bottomMargin: 16
                title: "账号"

                ColumnLayout {
                    width: parent.width
                    spacing: 8

                    Button {
                        Layout.fillWidth: true
                        text: "修改密码"
                    }

                    Button {
                        Layout.fillWidth: true
                        text: "退出登录"
                        onClicked: logoutDialog.open()
                    }
                }
            }
        }
    }

    // 清理缓存确认对话框
    Dialog {
        id: cacheDialog
        anchors.centerIn: parent
        title: "清理缓存"
        modal: true
        standardButtons: Dialog.Ok | Dialog.Cancel
        contentItem: Label {
            text: "确认清理所有缓存数据?"
            padding: 8
        }
        onAccepted: console.log("缓存已清理")
    }

    // 退出登录确认对话框
    Dialog {
        id: logoutDialog
        anchors.centerIn: parent
        title: "退出登录"
        modal: true
        standardButtons: Dialog.Yes | Dialog.No
        contentItem: Label {
            text: "确认退出当前账号?"
            padding: 8
        }
        onAccepted: console.log("已退出登录")
    }
}

总结

控件 用途 关键属性
Dial 旋钮调节 inputModestepSize
Tumbler 滚筒选择 modelvisibleItemCount
TabBar + StackLayout 选项卡切换 currentIndex 双向绑定
ToolBar 应用顶部工具栏 放在 header 属性
Dialog 模态对话框 standardButtonsmodal
Drawer 侧滑抽屉导航 edgeopen() / close()
Menu 上下文菜单 popup()MenuSeparator
ToolTip 悬停提示 delayvisible: hovered
StackView 页面栈前进/后退 push() / pop()
SwipeView 横划页面切换 配合 PageIndicator 使用
background / contentItem 自定义控件外观 替换任意一个,保留交互行为
相关推荐
Momo__1 小时前
VueUse createReusableTemplate —— 单文件组件内的模板复用神器
前端·vue.js
程序员小富1 小时前
我开源了一个开发者专属的智能 JSON 工具,得到了媳妇高度认可
前端·vue.js·后端
小小小小宇1 小时前
程序员如何给 LLM 装工具以及看懂推理过程
前端
写代码的皮筏艇1 小时前
React中的forwardRef
前端·react.js·面试
槑有老呆1 小时前
花三个月工资请了个 AI 程序员,结果它连青岛啤酒股价都查不了
前端
风骏时光牛马1 小时前
Verilog开发常见问题汇总解析
前端
子兮曰1 小时前
AI Coding Method Map:一张图看懂 AI 编程的完整链路
前端·人工智能·后端
weedsfly1 小时前
语法糖褪去之后——Babel 转译产物中的 JavaScript 本貌
前端·javascript
JustHappy2 小时前
「软件设计思想杂谈🤔」“切图仔”也能懂编译原理?框架源码也许没那么难。聊聊 Vue 的编译(上)
前端·javascript·vue.js