QML SplitView 进阶:嵌套布局与状态持久化

一个 SplitView 只能把区域分成两栏,遇到三分栏这种结构,就得在 SplitView 上再套一个 SplitView。

另外,用户把分隔条布局弄乱了,想临时一键恢复成上次保存好的样子。

本文用2个例子解决上述问题。

嵌套布局

外层水平分两栏,右侧再垂直分上下两栏,三层面板自由拖拽。如图所示:

演示代码

qml 复制代码
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

FadeInAnimation {
    id: root
    property color mainColor: Theme.listColors[Theme.demo3]

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

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

        Rectangle {
            Layout.fillWidth: true
            Layout.preferredHeight: 45
            color: Theme.demoStyle.frameBg
            radius: 6
            border.color: Theme.demoStyle.frameBorder
            border.width: 1

            RowLayout {
                anchors.fill: parent
                anchors.margins: 10
                spacing: 10

                Text {
                    text: "布局结构:"
                    font.pointSize: 10
                    color: Theme.demoStyle.frameText
                }

                Text {
                    text: "左侧面板 + 右侧(上下面板)"
                    font.pointSize: 10
                    color: root.mainColor
                    font.bold: true
                }

                Item { Layout.fillWidth: true }
            }
        }

        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: Theme.demoStyle.frameBg
            border.color: Theme.demoStyle.frameBorder
            border.width: 1
            radius: 6

            SplitView {
                id: horizontalSplit
                anchors.fill: parent
                anchors.margins: 10
                orientation: Qt.Horizontal

                handle: Rectangle {
                    implicitWidth: 6
                    implicitHeight: 6
                    color: root.mainColor
                    opacity: SplitHandle.pressed ? 1.0 : 0.7
                }

                // 面板1
                CustomRect {
                    SplitView.minimumWidth: 60
                    SplitView.minimumHeight: 60
                    rectColor: Qt.color(root.mainColor).darker(1.0)
                    rectNum: "1"
                }

                SplitView {
                    id: verticalSplit
                    SplitView.fillWidth: true
                    SplitView.fillHeight: true
                    SplitView.minimumWidth: 80
                    SplitView.minimumHeight: 80
                    orientation: Qt.Vertical

                    handle: Rectangle {
                        implicitWidth: 6
                        implicitHeight: 6
                        color: root.mainColor
                        opacity: SplitHandle.pressed ? 1.0 : 0.7
                    }

                    // 面板2
                    CustomRect {
                        SplitView.minimumWidth: 80
                        SplitView.minimumHeight: 80
                        rectColor: Qt.color(root.mainColor).darker(1.2)
                        rectNum: "2"
                    }

                    // 面板3
                    CustomRect {
                        SplitView.minimumWidth: 80
                        SplitView.minimumHeight: 80
                        rectColor: Qt.color(root.mainColor).darker(1.4)
                        rectNum: "3"
                    }
                }
            }
        }
    }
}

嵌套的写法就是把一个 SplitView 当成普通子项,写进另一个 SplitView 里。

这里有两个新手容易踩的坑

内层 SplitView 作为子项,一定要给自己配 SplitView.fillWidth / SplitView.fillHeight(或者至少给个 SplitView.minimumWidth),否则外层不会给它分配空间,右边的区域会塌掉;每个面板和每个嵌套层最好也都写上最小尺寸,拖拽时才不会把某一栏拖没。

拖拽是联动的,拖外层分隔条,整个右侧区域一起变宽变窄;拖内层分隔条,只影响右栏上下两部分。嵌套的层级就是布局的层级,逐层理解就不会乱。

适用场景:IDE 式多面板界面、复杂工作区、编辑器类布局。

状态持久化

把拖好的面板比例保存起来,下次一键恢复。如图所示:

演示代码

qml 复制代码
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

FadeInAnimation {
    id: root
    property color mainColor: Theme.listColors[Theme.demo4]

    property var savedState: null
    property bool hasSavedState: false
    property bool splitChanged: false

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

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

        Rectangle {
            Layout.fillWidth: true
            Layout.preferredHeight: 50
            color: Theme.demoStyle.frameBg
            radius: 6
            border.color: Theme.demoStyle.frameBorder
            border.width: 1

            RowLayout {
                anchors.fill: parent
                anchors.margins: 10
                spacing: 10

                Text {
                    text: "状态:"
                    font.pointSize: 10
                    color: Theme.demoStyle.frameText
                }

                // 显示保存状态
                Text {
                    text: root.hasSavedState ? "已保存" : "未保存"
                    font.pointSize: 10
                    font.bold: true
                    color: root.mainColor
                }

                Item {
                    Layout.fillWidth: true
                }

                // 在移动splitview时,状态闪烁
                Text {
                    text: "Resizing"
                    font.pointSize: 10
                    font.bold: true
                    color: root.mainColor
                    visible: splitView.resizing

                    SequentialAnimation on opacity {
                        loops: Animation.Infinite
                        running: splitView.resizing
                        NumberAnimation { to: 0.3; duration: 500 }
                        NumberAnimation { to: 1.0; duration: 500 }
                    }
                }

                CustomButton {
                    text: "保存状态"
                    // 解决第一次保存失效的问题,必须先移动splitview
                    enabled: root.splitChanged
                    bgNormal: root.mainColor
                    bgPressed: Qt.color(root.mainColor).darker(1.2)
                    onClicked: {
                        root.savedState = splitView.saveState()
                        root.hasSavedState = true
                        console.log("SplitView状态已保存")
                    }
                }

                CustomButton {
                    text: "恢复状态"
                    enabled: root.hasSavedState
                    bgNormal: root.mainColor
                    bgPressed: Qt.color(root.mainColor).darker(1.2)
                    onClicked: {
                        if (root.savedState) {
                            splitView.restoreState(root.savedState)
                            console.log("SplitView状态已恢复")
                        }
                    }
                }
            }
        }

        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: Theme.demoStyle.frameBg
            border.color: Theme.demoStyle.frameBorder
            border.width: 1
            radius: 6

            SplitView {
                id: splitView
                anchors.fill: parent
                anchors.margins: 10
                orientation: Qt.Horizontal

                handle: Rectangle {
                    implicitWidth: 6
                    implicitHeight: 6
                    color: root.mainColor
                    opacity: SplitHandle.pressed ? 1.0 : 0.7
                }

                onResizingChanged: {
                    root.splitChanged = true
                }

                // 面板1
                CustomRect {
                    SplitView.minimumWidth: 60
                    rectColor: Qt.color(root.mainColor).darker(0.9)
                    rectNum: "1"
                }

                // 面板2
                CustomRect {
                    SplitView.fillWidth: true
                    SplitView.minimumWidth: 60
                    rectColor: Qt.color(root.mainColor).darker(1.1)
                    rectNum: "2"
                }

                // 面板3
                CustomRect {
                    SplitView.minimumWidth: 60
                    rectColor: Qt.color(root.mainColor).darker(1.3)
                    rectNum: "3"
                }
            }
        }
    }
}

SplitView 自带两个方法:saveState() 把当前所有分隔条的位置打包成一个对象,restoreState() 按这个对象恢复布局。

demo 里"保存状态"按钮把结果存进 savedState,"恢复状态"按钮再把它交还给 restoreState(),拖好的比例就回来了。

demo演示的是临时处理,把记住布局真正落地,还要把 savedState 序列化存到本地(比如转成 JSON 写进设置文件),程序启动时读出来调 restoreState(),用户上次拖好的布局就自动回来了。

适用场景:编辑器布局记忆、窗口布局偏好、多面板工具的个性化设置。


已验证环境:

相关推荐
布莱克60518 小时前
QtWidgets 与 QML:两大 UI 开发框架的全面对比
开发语言·c++·qt·ui·架构
qq_4017004119 小时前
Qt 实时大数据量存储:自己动手实现 TDMS 读写
数据库·qt
NiceBlueChai19 小时前
Qt 跨线程传递多容器嵌套结构:一次 Release 实测
开发语言·qt
扶尔魔ocy19 小时前
【QT android】模仿行车记录仪案例
android·qt
zhanghaha13141 天前
Qt 类本质就是 C++ 的类,只是 Qt 框架扩展了一套【QObject 对象模型】
qt
Cx330❀2 天前
深入浅出 Qt 绘图机制:从 QPainter 到四大图像类解析
开发语言·qt·ui·性能优化·图形渲染
Littlehero_1212 天前
QT自定义控件之水泥厂组态(源码开源)
开发语言·qt
代钦塔拉2 天前
【C++/Qt 进阶】设计模式系列(6):桥接模式——把“两个独立变化的维度“拆开
c++·qt·设计模式
156082072192 天前
银河麒麟 Qt 可执行文件打包说明
开发语言·驱动开发·qt
Quz2 天前
QML GridView 布局:网格排布与排列方向
qt