Search code examples
pythonqtqmlpyside6

Efficient way to switch pyside6 qml screens


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


Solution

  • 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.