mayadev
mayadev

Reputation: 21

QML - Recursive path for nested StackViews

I have a QML code containing nested StackViews. I'm trying to set up a dynamic and automatic "path" system that would display, at the top of the screen, the path traveled to reach the current view, including the child StackViews. For instance, with the following code, we would see displayed: Path: Home > Module 1 > Sub-Module 3 > Data Page.

Ideally, I would also like to make it so that clicking on a name in the path takes us to that page, in addition to having a back button that navigates one page back. Therefore, the solution of adding the objectName property to a dynamically updated list is not really viable, as it is not very flexible.

Here is my current code, where the path only works for the first StackView. How could I add this functionality?

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15

ApplicationWindow {
    visible: true
    width: 800
    height: 600
    title: "StackView With Dynamic Path"

    ColumnLayout {
        anchors.fill: parent

        RowLayout {
            id: pathBar
            spacing: 5
            Layout.fillWidth: true
            Text {
                text: "Path :"
                font.bold: true
            }
            Repeater {
                model: stackPath
                Row {
                    spacing: 5
                    Text {
                        text: modelData
                    }
                    Text {
                        visible: index < stackPath.count - 1
                        text: ">"
                    }
                }
            }
        }

        // StackView principal
        StackView {
            id: mainStackView
            Layout.fillWidth: true
            Layout.fillHeight: true
            initialItem: homeComponent
            clip: true

            Component {
                id: homeComponent
                Page {
                    objectName: "Home"
                    Rectangle {
                        color: "lightblue"
                        anchors.fill: parent
                        Text {
                            anchors.centerIn: parent
                            text: "Homepage"
                        }
                        Button {
                            text: "Go to Module 1"
                            anchors.bottom: parent.bottom
                            anchors.horizontalCenter: parent.horizontalCenter
                            onClicked: mainStackView.push(module1Component)
                        }
                    }
                }
            }

            Component {
                id: module1Component
                Page {
                    objectName: "Module 1"
                    StackView {
                        id: module1StackView
                        anchors.fill: parent
                        initialItem: subModuleComponent
                        Connections {
                            target: module1StackView
                            onCurrentItemChanged: updatePath()
                            onPush: updatePath()
                            onPop: updatePath()
                        }

                        Component {
                            id: subModuleComponent
                            Page {
                                objectName: "Sub-Module 3"
                                Rectangle {
                                    color: "lightgreen"
                                    anchors.fill: parent
                                    Text {
                                        anchors.centerIn: parent
                                        text: "Sub-Module 3"
                                    }
                                    Button {
                                        text: "Go to Data Page"
                                        anchors.bottom: parent.bottom
                                        anchors.horizontalCenter: parent.horizontalCenter
                                        onClicked: module1StackView.push(dataPageComponent)
                                    }
                                    Button {
                                        text: "Back"
                                        anchors.bottom: parent.bottom
                                        anchors.left: parent.left
                                        onClicked: module1StackView.pop()
                                    }
                                }
                            }
                        }

                        Component {
                            id: dataPageComponent
                            Page {
                                objectName: "Data Page"
                                Rectangle {
                                    color: "lightcoral"
                                    anchors.fill: parent
                                    Text {
                                        anchors.centerIn: parent
                                        text: "Data Page"
                                    }
                                    Button {
                                        text: "Back"
                                        anchors.bottom: parent.bottom
                                        anchors.left: parent.left
                                        onClicked: module1StackView.pop()
                                    }
                                }
                            }
                        }
                    }
                    Button {
                        text: "Back"
                        anchors.bottom: parent.bottom
                        anchors.left: parent.left
                        onClicked: mainStackView.pop()
                    }
                }
            }
        }
    }

    ListModel {
        id: stackPath
    }

    function updatePath() {
        stackPath.clear()
        buildPath(mainStackView, "")
    }

    function buildPath(stackView, currentPath) {
        for (var i = 0; i < stackView.depth; i++) {
            var currentItem = stackView.get(i)
            currentPath += currentItem.objectName
            stackPath.append({ name: currentItem.objectName })
            var childStackView = currentItem.children.find(function(item) {
                return item instanceof StackView
            })
            if (childStackView && childStackView.currentItem) {
                buildPath(childStackView, currentPath + " > ")
            }
        }
    }

    Connections {
        target: mainStackView
        onCurrentItemChanged: updatePath()
        onPush: updatePath()
        onPop: updatePath()
    }

    Component.onCompleted: updatePath()
}

Edit: New code to include Stephen's answer :

main.qml:

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

ApplicationWindow {
    visible: true
    width: 800
    height: 600
    title: "StackView with dynamic path"

    Page {
        anchors.fill: parent
        header: Frame {
            ListView {
                implicitWidth: parent.width
                implicitHeight: 32
                model: stackView.children
                orientation: ListView.Horizontal
                delegate: Button {
                    background: Item { }
                    text: (index > 0 ? "> " : "") + modelData.title
                    font.underline: true
                    onClicked: {
                        while (stackView.depth > index + 1)
                            stackView.pop();
                    }
                }
            }
        }
        StackView {
            id: stackView
            anchors.fill: parent
            initialItem: homeComponent

            Component {
                id: homeComponent
                Home {
                }
            }

            Component {
                id: module1Component
                Module1 {
                }
            }
        }
        footer: Frame {
            RowLayout {
                Button {
                    text: "Pop"
                    enabled: stackView.depth > 1
                    onClicked: stackView.pop()
                }
                Button {
                    text: "Module 1"
                    onClicked: stackView.push(module1Component)
                }
            }
        }
    }
}

Home.qml :

import QtQuick
import QtQuick.Controls

Page {
    title: "Home"

    Text {
        anchors.centerIn: parent
        text: "Home"
    }
}

Module1.qml :

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

Page {
    title: "Module 1"

    StackView {
        id: module1StackView
        anchors.fill: parent
        initialItem: subModule1Component

        Component {
            id: subModule1Component
            SubModule1 {
            }
        }
    }
    footer: Frame {
        RowLayout {
            Button {
                text: "Pop"
                enabled: module1StackView.depth > 1
                onClicked: module1StackView.pop()
            }
            Button {
                text: "Sub-Module 3"
                onClicked: module1StackView.push(subModule1Component)
            }
        }
    }
}

SubModule1.qml :

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

Page {
    title: "Sub-Module 1"

    StackView {
        id: subModule1StackView
        anchors.fill: parent
        initialItem: dataPageComponent

        Component {
            id: dataPageComponent
            DataPage {
            }
        }
    }
    footer: Frame {
        RowLayout {
            Button {
                text: "Pop"
                enabled: subModule1StackView.depth > 1
                onClicked: subModule1StackView.pop()
            }
            Button {
                text: "Data Page"
                onClicked: subModule1StackView.push(dataPageComponent)
            }
        }
    }
}

DataPage.qml :

import QtQuick
import QtQuick.Controls

Page {
    title: "Data Page"

    Text {
        anchors.centerIn: parent
        text: "Data Page"
    }
}

Upvotes: 0

Views: 42

Answers (1)

Stephen Quan
Stephen Quan

Reputation: 26096

Seems you have a workable solution, but I recommend the following changes:

  • you don't need a ListModel use stackView.children
  • swap Repeater for ListView with horizontal orientation
  • use Page.title instead of Page.objectName
  • use a Button with an onClicked event for popping the pages
ListView {
    model: stackView.children
    orientation: ListView.Horizontal
    delegate: Button {
        text: (index > 0 ? "> " : "") + modelData.title
        onClicked: {
            while (stackView.depth > index + 1)
                 stackView.pop();
            }
        }
    }
}

You can Try it Online!

Upvotes: 0

Related Questions