Search code examples
pythonpython-3.xqmlpysidepyside6

No connection between qml and python in pyside6


I was writing a large program and encountered strange errors. Wandering around Google, I came to a simple program that does not perform the expected functionality.

When you press a button, the number should increase and be displayed in the console and in the field. But for some reason the number is only displayed in the console. Moreover, if in qml you replace the function call with any line, then the line after clicking will be shown.

So this is interface in qml

import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    width: 400
    height: 200
    title: "PySide QML Example"

    Rectangle {
        anchors.fill: parent

        Button {
            text: "Increment"
            onClicked: {
                textField.text = backend.increment_counter().toString();
            }
        }

        TextField {
            id: textField
            anchors.centerIn: parent
            readOnly: true
        }
    }
}

And this is simple program

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


class Backend(QObject):
    def __init__(self):
        super().__init__()
        self.counter = 0

    @Slot()
    def increment_counter(self):
        self.counter += 1
        print(self.counter)
        return self.counter


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

    backend = Backend()

    engine = QQmlApplicationEngine()
    engine.rootContext().setContextProperty("backend", backend)

    qml_file = "main.qml"
    engine.load(QUrl.fromLocalFile(qml_file))

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

    sys.exit(app.exec())

I tried reinstalling pyside packages. And perhaps this will help: in a large program, the Component.onCompleted method did not work for me and methods from Python that were written inside qml were not called


Solution

  • As per @musicamante comment, it would be better for you provide a property with a change signal alongside your method:

    • counter Property()
    • counter_changed Signal()
    • increment_counter() Slot()

    I cleaned up your implementation of increment_counter to perform the increment but not return the value. The returning of the value will be done through the property.

    As a property, we can clean up the QML code so that the Button onClicked only invokes the slot, but, the TextField is directly bound to the counter property.

    # main.py
    import sys
    from PySide6.QtCore import Qt, QUrl, Slot, QObject, Property, Signal
    from PySide6.QtGui import QGuiApplication
    from PySide6.QtQml import QQmlApplicationEngine
    
    class Backend(QObject):
        def __init__(self):
            super().__init__()
            self._counter = 0
        counter_changed = Signal()
        def get_counter(self):
            return self._counter
        def set_counter(self, val):
            self._counter = val
            self.counter_changed.emit()
        @Slot()
        def increment_counter(self):
            self.set_counter(self.get_counter() + 1)
        counter = Property(int, get_counter, set_counter, notify=counter_changed)
    
    if __name__ == "__main__":
        app = QGuiApplication(sys.argv)
        backend = Backend()
        engine = QQmlApplicationEngine()
        engine.rootContext().setContextProperty("backend", backend)
        qml_file = "main.qml"
        engine.load(QUrl.fromLocalFile(qml_file))
        if not engine.rootObjects():
            sys.exit(-1)
        sys.exit(app.exec())
    
    // main.qml
    import QtQuick 2.15
    import QtQuick.Controls 2.15
    
    ApplicationWindow {
        visible: true
        width: 400
        height: 200
        title: "PySide QML Example"
    
        Rectangle {
            anchors.fill: parent
    
            Button {
                text: "Increment"
                onClicked: backend.increment_counter()
            }
    
            TextField {
                id: textField
                anchors.centerIn: parent
                readOnly: true
                text: backend.counter
            }
        }
    }