Reputation: 21
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
Reputation: 26096
Seems you have a workable solution, but I recommend the following changes:
ListModel
use stackView.children
Repeater
for ListView
with horizontal orientationPage.title
instead of Page.objectName
Button
with an onClicked
event for popping the pagesListView {
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