Efficient way to switch pyside6 qml screens

Question:

I am working on a project where I have to make a multi-screen/window application. I am using QML and PySide6 for this purpose.
The flow of my applications is like below:

login screen -> search screen (search for certain items) -> test screen (performs some operations) -> reports screen (displays results from test screen).

Currently what I am doing is using a main.py file to load the login page:

import os
from pathlib import Path
import sys
from api_login_test import get_credentials
# from login_data import get_credentials

from PySide6.QtCore import QCoreApplication, Qt, QUrl, QObject
from PySide6.QtWidgets import QApplication, QLineEdit, QPushButton
from PySide6.QtQml import QQmlApplicationEngine

CURRENT_DIRECTORY = Path(__file__).resolve().parent


def main():
    app = QApplication(sys.argv)
    engine = QQmlApplicationEngine()

    credentials = get_credentials()

    engine.rootContext().setContextProperty('credentials', credentials)
    login_file = os.fspath(CURRENT_DIRECTORY / "qml" / "login.qml")
    url = QUrl.fromLocalFile(login_file)

    def handle_object_created(obj, obj_url):
        if obj is None and url == obj_url:
            QCoreApplication.exit(-1)

    engine.objectCreated.connect(handle_object_created, Qt.QueuedConnection)
    engine.load(url)
    
    sys.exit(app.exec())


if __name__ == "__main__":
    main()

once the login page opens up now for switching rest of the screens, I use javascript function I wrote in all my qml files to switch to next screen. Following is the JS code I am using for switching to next screen.

function switch_to_test(access)   {
                if (access == true) {
                    var component = Qt.createComponent("test_interface_dark_debug.qml")
                    var win = component.createObject()
                    win.show()
                    visible = false
                    }
                else {
                    console.log('Invalid Credentials')
                }
                }

And this is causing my UI to crash as I reach test screen. So is there a way to efficiently switching the screens without my UI crashing?
I found out this explanation for the crashing of my UI, but I don’t know how to resolve the issue.
Thank you

Asked By: lakkadaayush

||

Answers:

The following is a showcase of various approaches regarding to your question.
Although all the code here is coherent and meant to be run together, It shows different approaches to navigate pages and you may adopt what you like.

enter image description here


Setup:

File main.py:

import sys
from pathlib import Path
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine


if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    qml_file = Path(__file__).parent / "main.qml"
    engine.load(qml_file)

    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec())

Method 1: Use a StackView:

File main.qml:

import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Material 2.15

ApplicationWindow {
    id: mainFrame
    width: 640
    height: 480
    visible: true
    title: qsTr("Windows handeling in QML")
    Material.theme: Material.Dark

    StackView{id: stack_view
        initialItem: logginWin
        anchors.fill: parent;
        Component{id: logginWin
            LoginWin{
                onLoggedIn: {
                    stack_view.push(stack_le)
                    console.log("logged In")
                }
            }
        }
        Component{id: stack_le
            StackLayoutWin{
                onReturnToLogginWin:{
                    stack_view.pop()
                }
            }
        }
    }
}

File LoginWin.qml:

import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Material 2.15

Item{id: root
    anchors.fill: parent;
    signal loggedIn;

    TextArea{id: input_
        placeholderText: "Enter password"
        anchors.centerIn: parent
    }
    Button{
        text: "Login";

        anchors {
            horizontalCenter: parent.horizontalCenter;
            top: input_.bottom
        }
        onClicked: {
            console.log(input_.text)

            if(input_.text == "12345")
            {
                root.loggedIn()
            }
            else{
                input_.text = "Wrong password"
            }
        }
    }
}
Pros Cons
Default animation You will have to pop / append windows while navigating, That makes it inconvenient for multi-directional pages workflow.
Very easy for one direction pages workflow that also can be reversed. Not very customizable.

Method 2 Use StackLayout With TabBar:

Here would be our "main application" window.
You would have here different pages the user would be able to navigate.

File StackLayoutWin.qml:

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Controls.Material 2.15

Item{id: root
    signal returnToLogginWin;
    anchors.fill: parent;
    Button{id: return_to_StackView
        x: parent.width - width;
        y: tab_bar.y
        text: "return to login page"
        onClicked: {
            root.returnToLogginWin()
        }
    }
    ColumnLayout{
        anchors.fill: parent;
        TabBar {
            id: tab_bar
            TabButton {
                width: 100
                text: qsTr("foo")
            }
            TabButton {
                width: 100
                text: qsTr("bar")
            }
            TabButton {
                width: 100
                text: qsTr("Loader")
            }
        }


        StackLayout {
            id: stack_layout
            currentIndex: tab_bar.currentIndex
            Rectangle {
                color: 'teal'
                Label{
                    anchors.centerIn: parent
                    text: "Page " + stack_layout.currentIndex
                }
                implicitWidth: 200
                implicitHeight: 200
            }
            Rectangle {
                color: 'plum'
                implicitWidth: 300
                implicitHeight: 200
                Label{
                    anchors.centerIn: parent
                    text: "Page " + stack_layout.currentIndex
                }
            }
            JustALoader{}
        }

    }
}
Pros Cons
Ability to navigate threw different pages simultaneously Harder to implement than StackView
Not very customizable.

Method 3 Use Loader:

File JustALoader.qml:

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Controls.Material 2.15

Item{
    anchors.fill: parent;
    ColumnLayout{
        anchors.fill: parent;
        Row{
            Button{
                Layout.fillWidth: true;
                text: " To cyan rect"
                onClicked: {
                    loader_.sourceComponent = cyan_rect
                }
            }
            Button{
                Layout.fillWidth: true;
                text: " To red rect"
                onClicked: {
                    loader_.sourceComponent = red_rect
                }
            }
        }
        Component{id: cyan_rect
            Rectangle{
                color: "cyan"
            }
        }
        Component{id: red_rect
            Rectangle{
                color: "red"
            }
        }
        Loader{id: loader_
            Layout.fillWidth: true;
            Layout.fillHeight: true;
            sourceComponent: red_rect
        }
    }
}
Pros Cons
Customizable Not very useful "out of the box"

You can find this example here

Note:

  • I generally don’t use QtQuick.Controls because I think it holds you back at a certain point. So I would recommend creating your own page-management when needed using the Loader element and other components.
Answered By: ניר
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.