How to emit Signal from nested QML page to python

Question:

in my QML/python app I can emit signal from main.qml to the python code. But now In main.qml I added StackLayout for loading another page1.qml. In that page1.qml is button, now I want to emit signal from this button to the python.

I use this method for emit signals from main.qml file to the python:
But do not know how to emit it from nested page1.qml

main.py

from PySide2.QtCore import QObject, QUrl, Slot, Signal, Qt
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine

class Foo(QObject):
    @Slot(str)
    def test_slot(self, input_string : str):
        print(input_string)

if __name__ == "__main__":
    import os
    import sys

    app = QGuiApplication()
    foo = Foo()
    engine = QQmlApplicationEngine()
    
    #CHANGES: line excluded engine.rootContext().setContextProperty("foo", foo)
    
    qml_file = "main.qml"
    current_dir = os.path.dirname(os.path.realpath(__file__))
    filename = os.path.join(current_dir, qml_file)
    engine.load(QUrl.fromLocalFile(filename))
    if not engine.rootObjects():
        sys.exit(-1)
    
    #CHANGES: connect QML signal to Python slot
    engine.rootObjects()[0].test_signal.connect(foo.test_slot, type=Qt.ConnectionType.QueuedConnection)
    
    sys.exit(app.exec_())

main.qml

import QtQuick 2.13
import QtQuick.Controls 2.13

ApplicationWindow {
    visible: true
    
    //CHANGES: declare signal
    signal test_signal(string input_string)

    Button {
        anchors.centerIn: parent
        text: "Example"

        //CHANGES: emit signal
        onClicked: test_signal("Test string")
    }
}

https://stackoverflow.com/a/69595659/5166312

Thank you very much.

Asked By: WITC

||

Answers:

  1. Add objectName string property for nested pages (included the case if they are in separate files):
    objectName: "page1_objname"
    
  2. Access nested pages from Python (or C++) backend
    qmlpage1 = engine.rootObjects()[0].findChild(QObject, "page1_objname")
    
  3. Connect slot and signal as usually in Python or C++ backend (you already have code in your question or in my answer https://stackoverflow.com/a/69595659/5166312).
    qmlpage1.page1_signal.connect(
       test_object.test_slot3,
       type=Qt.ConnectionType.QueuedConnection)
    

The whole project with StackLayout, two nested pages, 3 signals and 3 slots in my test example on https://github.com/Ornstein89/pyside6-qml-slotsignal.

main.py

# This Python file uses the following encoding: utf-8
import sys
from pathlib import Path

from PySide6.QtCore import QObject, QUrl, Slot, Signal, Qt
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine

class TestClass(QObject):
    '''
    Object - slot-owner and signal-acceptor
    '''
    @Slot(str)
    def test_slot1(self, input_string : str):
        print(input_string)
    @Slot(str)
    def test_slot2(self, input_string : str):
        print(input_string)
    @Slot(str)
    def test_slot3(self, input_string : str):
        print(input_string)

if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()
    test_object = TestClass()
    qml_file = Path(__file__).resolve().parent / "main.qml"
    engine.load(qml_file)
    if not engine.rootObjects():
        sys.exit(-1)

    # !!! connect ApplicationWindow.mainapp_signal() to test_object.test_slot1
    engine.rootObjects()[0]
        .mainapp_signal.connect(
            test_object.test_slot1,
            type=Qt.ConnectionType.QueuedConnection)

    # !!! access nested page1 from python backend
    qmlpage1 = engine.rootObjects()[0].findChild(QObject, "page1_objname")

    # !!! and connect MyPage1.page1_signal() to test_object.test_slot2
    qmlpage1.page1_signal.connect(
            test_object.test_slot2,
            type=Qt.ConnectionType.QueuedConnection)

    # !!! access nested page2 from python backend
    qmlpage2 = engine.rootObjects()[0].findChild(QObject, "page2_objname")

    # !!! and connect MyPage2.page2_signal() to test_object.test_slot3
    qmlpage2.page2_signal.connect(
            test_object.test_slot3,
            type=Qt.ConnectionType.QueuedConnection)

    sys.exit(app.exec())

main.qml

import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Layouts

ApplicationWindow {
    width: 480
    height: 640
    visible: true
    title: qsTr("Example for Stackoverflow")

    // !!! singal №1 in root QML Object - most easy to connect in main.py
    signal mainapp_signal(string input_string)

    StackLayout {
        id : stacklayout
        anchors.fill: parent
        MyPage1 {
            id: page1
        }
        MyPage2 {
            id: page2
        }
    }
}

MyPage1.qml

import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Layouts

Page{
    id: page1

    // !!! singal №2 in nested QML Object
    signal page1_signal(string input_string)

    // !!! important - this name is used
    // to access page1 from C++ or Python backend
    objectName: "page1_objname"

    Button{
        text: "Go to page 2"
        anchors.centerIn: parent
        onClicked: {
            stacklayout.currentIndex = 1
            // call root object signal from nested object
            mainapp_signal("mainapp_signal() from page1");
            // call nested object signal from same object
            page1_signal("page1_signal() from page1");
            // call another nested object signal
            page2.page2_signal("page2_signal() from page1");
        }
    }
}

MyPage2.qml

import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Layouts

Page{
    id: page2

    // !!! singal №3 in nested QML Object
    signal page2_signal(string input_string)

    // !!! important - this name is used
    // to access page2 from C++ or Python backend
    objectName: "page2_objname"

    Button{
        text: "Go to page 1"
        anchors.centerIn: parent
        onClicked: {
            stacklayout.currentIndex = 0
        }
    }
}
Answered By: Ornstein89

You can add a Conections element to use signals of loaded component.
Look at documentation here.

Also, you can declare a component for your page1.qml item and use sourceComponent property of Loader. In this way you can use signal inside component to call an outer method. Look at documentation here.

Answered By: S.M.Mousavi
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.