2

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

  • Why would you create new windows and not just use something like [`StackView`](https://doc.qt.io/qt-6/qml-qtquick-controls2-stackview.html) or a [`StackLayout`](https://doc.qt.io/qt-6/qml-qtquick-layouts-stacklayout.html)? – ניר Jul 17 '22 at 11:59
  • Also there is a QML type for [Window](https://doc.qt.io/qt-6/qml-qtquick-window.html#details) – ניר Jul 17 '22 at 12:01
  • Didn't think of using StackVIew/StackLayout in that manner. But as per the link I mentioned, it says that they also work upto 2 screens. Can you point me out to any example regarding the same where more than 3 screens are used? – lakkadaayush Jul 17 '22 at 13:39
  • @ניר I am using ApplicationWindow in place of Window. Will changing it to Window help? – lakkadaayush Jul 17 '22 at 13:40

1 Answers1

1

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.
ניר
  • 1,204
  • 1
  • 8
  • 28
  • Thank you for the response, I'll try this out today and let you know. Just one quick question, if I have a third screen that I wish to load after processing the second screen so should I create a signal in second screen and add second screen in the main.qml and puch the third screen? – lakkadaayush Jul 18 '22 at 03:13
  • @lakkadaayush It is a possibility... You can also call Python backed to handle pages. – ניר Jul 18 '22 at 03:58
  • I tried your example, It worked fine till second screen, but when I invoked a signal from second screen, in order to switch to third screen, main.qml didn't catch the signal. It got stuck till 2nd screen only. – lakkadaayush Jul 18 '22 at 04:08
  • There is no third screen here, wym? – ניר Jul 18 '22 at 04:09
  • please check the edit. – lakkadaayush Jul 18 '22 at 05:01
  • @lakkadaayush I would love to help you though it is not related to the original question so you would need to open a new question with an appropriate Header. *Consider removing that edit.* – ניר Jul 18 '22 at 05:28
  • Also there are more alternatives I am about to add to my answer. – ניר Jul 18 '22 at 05:30
  • Sure, I'll create a new question for this. – lakkadaayush Jul 18 '22 at 05:37