Connect QML signal to PySide2 slot

Question:

I have some expirience using Qt/C++ and now I want to switch to PySide2 + QML. I want to connect ui signals, such as clicking a button, to python slot

I have seen many examples, but they all differ, i guess PyQt/PySide is changing quickly now

Can you provide me modern and clean way of connecting a QML signal to PySide Slot? For example clicking a Button to printing some text in python console. Here’s my simple code example

main.py

from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine

def test_slot(string): # pseudo slot
    print(string)

if __name__ == "__main__":
    app = QGuiApplication()
    engine = QQmlApplicationEngine('main.qml')
    exit(app.exec_())

main.qml

import QtQuick 2.13
import QtQuick.Controls 2.13

ApplicationWindow {
    visible: true

    Button {
        anchors.centerIn: parent
        text: "Example"
        onClicked: test_slot("Test") //pseudo signal
    }
}
Asked By: Kot Shrodingera

||

Answers:

The best practice in these cases is to create a QObject, export it to QML and make the connection there as it is also done in C++.

main.py

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


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


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

    app = QGuiApplication()
    foo = Foo()
    engine = QQmlApplicationEngine()
    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)
    sys.exit(app.exec_())

main.qml

import QtQuick 2.13
import QtQuick.Controls 2.13

ApplicationWindow {
    visible: true

    Button {
        anchors.centerIn: parent
        text: "Example"
        onClicked: foo.test_slot("Test")
    }
}

Note: All C++/QML good practices also apply in Python/QML with minimal changes and restrictions.

Answered By: eyllanesc

The eyllanesc’s solution is direct synchronous call of method test_slot. It is good when test_slot is small and fast. But if it includes a lot of operations, QML GUI will be suspended each time until test_slot is returned.

The mosth Qt-like way is slot-signal metaobject connection (see #CHANGES and //CHANGES below):

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")
    }
}
Answered By: Ornstein89
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.