QML StackView:三种入栈方式(Item、Component 与 URL)

0 阅读5分钟

stack.push(参数) 里那个参数到底能传什么?

官方文档说它可以是一个 Item、一个 Component 或一个 url——三种传法对应三种典型的页面管理方式。

这篇用一个 demo 把三种 push 各做一遍,它们推入的都是同一个 PushItem.qml 页面,但加载路径完全不同:一种是"页面对象已在 QML 里声明好,直接推实例",另两种是"把页面当模板,用时再创建"。顺带看 push 返回的页面对象怎么用——返回后立刻 connect 它发出的信号,实现页面与栈的解耦回调。

  • Push Item — push 一个已存在的页面实例
  • Push ComponentQt.createComponent() 创建组件后 push
  • Push URL — push 一个 QML 文件地址,内部自动加载

三种 push 方式

5.gif

页面顶部三个按钮,分别演示 push 一个 Item、push 一个 Component、push 一个 URL。被推入的页面是同一个 PushItem.qml:白底圆角卡片,中间一行标题(颜色随 push 传入的参数变化)、一段说明文字和一个 Pop 按钮。点 Pop 会通知外层把这一页弹掉。

演示代码

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

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

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

        StackView {
            id: stack
            initialItem: mainView
            Layout.fillWidth: true
            Layout.fillHeight: true
            spacing: 15
            clip: true
        }
    }

    Component {
        id: mainView

        ColumnLayout {
            spacing: 10

            property color textColor: "#333"

            Button {
                text: "Push Item"
                Layout.fillWidth: true
                Layout.preferredHeight: 30
                onClicked: {
                    var info = {"textColor":"#3498db", "textTitle":"Push Item", "textInfo":"用于测试【Push Item】, 调用StackView - push" }
                    var page = stack.push(stackItem, info)
                    page.popClicked.connect(slotPopItem)
                }
            }

            Button {
                text: "Push Component"
                Layout.fillWidth: true
                Layout.preferredHeight: 30
                onClicked: {
                    var info = {"textColor":"#e74c3c", "textTitle":"Push Component", "textInfo":"用于测试【Component】, 调用Qt.createComponent()" }
                    var com = Qt.createComponent(Qt.resolvedUrl("PushItem.qml"))
                    if (com.status === Component.Ready) {
                        var page = stack.push(com, info)
                        page.popClicked.connect(slotPopItem)
                    }
                }
            }

            Button {
                text: "Push URL"
                Layout.fillWidth: true
                Layout.preferredHeight: 30
                onClicked: {
                    // 通过 URL 加载 PushItem.qml
                    var info = {"textColor":"#2ecc71", "textTitle":"Push URL", "textInfo":"用于测试【Push URL】- Qt.resolvedUrl" }
                    var page = stack.push(Qt.resolvedUrl("PushItem.qml"), info)
                    page.popClicked.connect(slotPopItem)
                }
            }

            Item { Layout.fillHeight: true }
        }
    }

    PushItem {
        id: stackItem
        visible: false
    }

    function slotPopItem() {
        stack.pop()
    }
}

被推入的页面 PushItem.qml

import QtQuick
import QtQuick.Layouts
import QtQuick.Controls

Rectangle {
    width: 250
    height: 250
    border.color: "#ccc"
    border.width: 0
    radius: 6
    visible: false

    property color textColor: "#333"
    property string textTitle: ""
    property string textInfo: ""
    signal popClicked

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

        Text {
            text: textTitle
            color: textColor
            font.pointSize: 13
            font.bold: true
        }

        Text {
            text: textInfo
            color: textColor
            font.pointSize: 11
            Layout.fillWidth: true
            Layout.preferredHeight: 60
            wrapMode: Text.Wrap
        }

        RoundButton {
            text: "Pop"
            Layout.preferredWidth: 90
            Layout.preferredHeight: 30
            onClicked: popClicked()
        }

        Item { Layout.fillHeight: true }
    }
}

关键逻辑解析

push 的第二个参数 properties 会把值注入页面同名属性。三个按钮都构造了一个 info 对象,键名是 textColortextTitletextInfo,而 PushItem 恰好声明了这三个 property。push 时传入的 properties 会按名赋给新页面——这就是为什么三种方式推入同一个 qml,标题文字和颜色却各不相同。属性注入让"同一页面模板 + 不同数据"的复用模式成立。

Push Item:直接推一个已存在的实例PushItem { id: stackItem; visible: false } 挂在文件根部但平时不可见,像一个"备用页面"。stack.push(stackItem, info) 把这个现成实例推入栈。适合页面对象已经声明好、需要重复使用同一份实例的场景——但要小心:它一直被复用的是同一个实例,适合"推入一次用完就弹"的页面,不适合需要同时存在多份副本的场景。

Push Component:先用 Qt.createComponent 造出组件Qt.createComponent(Qt.resolvedUrl("PushItem.qml")) 把 qml 文件编译成一个 Component 对象,组件就绪后 push 它——StackView 会为每次 push 创建一份页面实例。本地文件通常同步加载完成,但加载远程组件或文件较多时可能未就绪,所以稳妥起见先查 com.status === Component.Ready 再 push(这是 createComponent 用法的标准姿势,避免拿一个没加载好的组件去 push)。适用于"页面文件可能较多、想统一管理加载时机"的场景。

Push URL:把地址直接交给 StackViewstack.push(Qt.resolvedUrl("PushItem.qml"), info) 不用自己创建组件,StackView 内部完成加载和实例化,代码最省。三种方式最终都会返回"成为当前页的那个 Item",所以 var page = stack.push(...) 拿到的就是新页面对象。

page.popClicked.connect(slotPopItem) 是页面与栈解耦的关键。PushItem 内部只声明 signal popClicked,Pop 按钮点了就发信号——它并不知道 StackView 的存在,也不知道自己该被谁弹掉。外层拿到 push 返回的 page 后,把它的 popClicked 信号连到 slotPopItem,后者执行 stack.pop()。这样页面文件可独立复用(放哪个 StackView 里都能用),栈操作统一由外层负责。

enabled 与状态保护:这里没给按钮加 enabled 判断,因为每次 push 后当前页会被新页面盖住,按钮点不到,天然防止重复触发。

三种方式怎么选

方式加载时机页面实例适用场景
Push Item页面已创建好,随用随推复用同一个实例单份常驻页面、动态数据
Push Component手动 Qt.createComponent,可控制加载每次 push 新建需要管理加载状态、批量页面
Push URLStackView 内部自动加载每次 push 新建最省代码,页面独立成文件

日常开发里 Push URL 最常用——页面天然按文件拆分,push 时给个地址即可;需要先确认组件加载成功再动作时用 Component;页面对象已在界面里声明、只需要推一次的场景才用 Item。

运行验证

  1. Qt Creator 打开 qml_stackview/CMakeLists.txt,按 Ctrl+R 运行;
  2. 左侧点「Push对象」,依次点 Push Item / Push Component / Push URL;
  3. 观察每次推入的卡片标题与颜色不同(蓝/红/绿),点卡片上的 Pop 按钮,页面被外层 slotPopItem 弹回按钮页。

扩展复用方向

  • properties 注入不止传文本颜色,传数据对象、回调函数都能按名赋到页面属性上,做成"通用详情页 + 数据驱动";
  • 把 push 返回的 page 存起来,connect 它的多个信号,就能实现页面内确认框、表单提交后外层统一收尾的协作模式;
  • 页面文件多了以后,把 URL 字符串集中到一个路由表(按页面名查 URL),push 时只传名字,页面跳转逻辑更清晰。

已验证环境