QML 容器进阶:自定义 GroupBox、ScrollView 与多层嵌套

0 阅读5分钟

在上一篇文章中介绍了 Pane、Frame、GroupBox 这三个基础容器。这篇继续深入三个更实用的场景:自定义 GroupBox 外观、用 ScrollView 处理溢出内容,以及把多个容器组合成一个带 header/footer 的可滚动页面。

这三个 demo 更接近真实项目:自定义样式解决设计系统不一致的问题;ScrollView 解决内容超出视口的问题;多层嵌套则是日常布局最常见的组合方式。

自定义 GroupBox

默认 GroupBox 的标题和边框样式由当前控件风格决定。如果你的项目用了自定义主题,或者默认风格跟整体 UI 不搭,就需要通过 backgroundlabel 两个属性手动接管。

background 负责整个 GroupBox 的底框区域,label 负责标题栏。把它们拆开,可以让你分别控制:比如底框用圆角矩形、标题用加粗字体加图标,互不干扰。这比直接套一个 Rectangle 再手动写标题更省事,因为 GroupBox 已经帮你处理了内容边距和标题位置。

4.png

演示代码

import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts

FadeInAnimation {
    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 20
        spacing: 15

        // ... 省略标题组件 TitleSeparator ...

        ColumnLayout {
            Layout.fillWidth: true
            Layout.leftMargin: 20
            Layout.rightMargin: 20
            spacing: 15

            GroupBox {
                id: control
                title: "自定义GroupBox容器"
                Layout.fillWidth: true
                Layout.preferredHeight: 120

                background: Rectangle {
                    y: control.topPadding - control.bottomPadding
                    width: parent.width
                    height: parent.height - control.topPadding + control.bottomPadding
                    color: "transparent"
                    border.color: "#ccc"
                    radius: 4
                }

                label: Label {
                    x: control.leftPadding
                    width: control.availableWidth
                    text: control.title
                    elide: Text.ElideRight
                }

                Label {
                    anchors.fill: parent
                    text: "- 通过 background 自定义边框样式\n- 通过 label 自定义标题外观\n- 适合需要特殊样式的场景"
                    font.pointSize: 10
                    textFormat: Text.MarkdownText
                    wrapMode: Text.WordWrap
                }
            }
        }

        Item { Layout.fillHeight: true }
    }
}

关键逻辑解析

  • background 接收一个 Rectangle。这里把 y 设为 control.topPadding - control.bottomPadding,高度设为 parent.height - control.topPadding + control.bottomPadding,目的是让背景矩形对齐到内容区,避免遮住标题
  • label 接收一个 Label。示例里只是简单显示标题文字,但你可以加 Image 做图标、改 font 做样式、或者用 RowLayout 组合多个元素
  • import QtQuick.Controls.Basic 这个导入很重要。Basic 风格给自定义控件留下了最大的可控空间,不会被平台原生风格覆盖

自定义 GroupBox 适合需要统一设计系统的场景。一旦把样式抽成组件,整个项目的分组框都能保持一致。

ScrollView 容器

列表、长文本、动态生成的内容,都很难在一屏内放下。如果手动算高度再切滚动区域,代码会又臭又长。ScrollView 的作用就是接管这部分逻辑:只要内容超出视口,它就自动显示滚动条。

ScrollView 继承自 Pane,所以背景、内边距这些属性都还在。它的内容可以是任何 Item,常见的配合是 ColumnLayoutRepeater 做纵向列表,或者 RowLayoutRepeater 做横向卡片。

5.png

演示代码

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

FadeInAnimation {
    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 20
        spacing: 15

        // ... 省略标题组件 TitleSeparator ...

        ColumnLayout {
            Layout.fillWidth: true
            Layout.leftMargin: 20
            Layout.rightMargin: 20
            spacing: 15

            Rectangle {
                Layout.fillWidth: true
                Layout.preferredHeight: 200
                border.color: "#ccc"
                radius: 4

                ScrollView {
                    anchors.fill: parent
                    anchors.margins: 12
                    ScrollBar.vertical.policy: ScrollBar.AsNeeded

                    ColumnLayout {
                        spacing: 6

                        Repeater {
                            model: ["### ScrollView容器", "**特性:**", "- 继承自 Pane", "- 提供滚动功能", "- 自动处理内容溢出", "- 支持水平和垂直滚动", "- 可配置滚动条策略"]
                            Label {
                                required property string modelData
                                text: modelData
                                Layout.fillWidth: true
                                font.pointSize: 10
                                textFormat: Text.MarkdownText
                            }
                        }
                    }
                }
            }
        }

        Item { Layout.fillHeight: true }
    }
}

关键逻辑解析

  • ScrollBar.vertical.policy: ScrollBar.AsNeeded 让垂直滚动条按需显示。如果内容没超出,滚动条不会出现;超出后自动出现。其他可选值有 AlwaysOnAlwaysOff
  • ScrollView 本身不限制内容类型,但内容最好有确定的大小,否则滚动计算可能出错
  • 外层套一个固定高度的 Rectangle,用来限定 ScrollView 的显示区域。ScrollView 会基于这个区域决定什么时候该滚动

ScrollView 的长板是“不用算高度”。你把内容丢进去,它自己处理滚动逻辑。

多层嵌套组合

实际项目里很少单独用一个容器,更多是像搭积木一样组合。这个 demo 展示了一个很常见的结构:Page 做顶层页面,header 放标题栏,footer 放状态信息,中间的内容区用 ScrollView 包裹一行 Pane 卡片。

这种结构的好处是职责清晰:Page 管页面骨架,ScrollView 管滚动,Pane 管单个卡片的样式。哪个部分要改,就去改对应的组件,不会影响其他层。

6.png

演示代码

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

FadeInAnimation {
    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 20
        spacing: 15

        // ... 省略标题组件 TitleSeparator ...

        Page {
            Layout.fillWidth: true
            Layout.fillHeight: true

            header: Rectangle {
                width: parent.width
                height: 30
                color: "#f5f5f5"
                border.color: "#ccc"

                Text {
                    anchors.left: parent.left
                    anchors.leftMargin: 10
                    anchors.verticalCenter: parent.verticalCenter
                    font.pointSize: 10
                    text: "Page容器 - 多层嵌套"
                }
            }

            footer: Rectangle {
                width: parent.width
                height: 25
                border.color: "#ccc"

                Text {
                    anchors.centerIn: parent
                    font.pointSize: 9
                    text: "已创建 " + controlRepeater.count + " 个卡片"
                }
            }

            background: Rectangle {
                color: "#fff"
                border.color: "#ccc"
            }

            ScrollView {
                anchors.fill: parent
                anchors.margins: 15
                ScrollBar.horizontal.policy: ScrollBar.AsNeeded

                RowLayout {
                    spacing: 10

                    Repeater {
                        id: controlRepeater
                        model: 6
                        delegate: Pane {
                            contentWidth: 120
                            contentHeight: 120

                            background: Rectangle {
                                color: "#f5f5f5"
                                border.color: "#ccc"
                                radius: 4
                            }

                            Label {
                                anchors.centerIn: parent
                                text: "卡片 " + (index + 1)
                                font.bold: true
                                font.pointSize: 10
                            }
                        }
                    }
                }
            }
        }
    }
}

关键逻辑解析

这个结构可以拆成三层看:

  • Page — 顶层页面容器,提供 headerfooter 两个插槽。内容区剩下的空间交给子元素
  • ScrollView — 放在 Page 的内容区,负责横向滚动。因为卡片是横向排列的,所以启用水平滚动条
  • Pane — 作为 Repeater 的 delegate,每张卡片都是一个独立的小容器。contentWidthcontentHeight 确保卡片大小一致

controlRepeater.count 在 footer 里实时显示卡片数量。Repeater 的 count 属性会随 model 自动更新,比手动维护计数更可靠。

这个模式可以直接复用到很多场景:横向图片画廊、工具栏卡片、文件选择列表等。

运行验证

  1. Qt Creator 打开 qml_container/CMakeLists.txt
  2. Ctrl+R 运行
  3. 左侧导航切换 “自定义GroupBox”、“ScrollView”、“嵌套组合” 查看效果
  4. 在 ScrollView 里增加 Repeater 的 model 数量,观察滚动条自动出现

扩展复用方向

  • 自定义 GroupBox 的 label 里加图标,做成带图标的设置分组
  • Page + ScrollView + Pane 的组合可以改成纵向瀑布流
  • 给嵌套 demo 的 Pane 卡片加鼠标悬停效果
  • 把多层嵌套结构封装成 CardGallery 组件,传入 model 即可复用

已验证环境