Solved Communication between python and qml
-
Hello, I just started with python and qml and if have a problem with the communication between the qml and the py. In the following function in my python main app class only the first line works
def foo(self, aword): print('from qml: %s was received' % (aword)) o = self.win.findChild(QObject, 'lbl1') print("child: ", o) return 'a message from python'
I receive the text sent from the qml, but "self.win.findChild" returns None, and in the qml, the text sent by python is undefined
Here is the py file
# -*- coding: utf-8 -*- import sys import images_rc from PyQt5.QtWidgets import QApplication from PyQt5.QtQml import QQmlApplicationEngine from PyQt5.QtCore import QObject, pyqtSlot, QVariant class MainApp(QObject): def __init__(self, context, parent=None): super(MainApp, self).__init__(parent) self.win = parent self.ctx = context @pyqtSlot(QVariant) def foo(self, aword): print('from qml: %s was received' % (aword)) o = self.win.findChild(QObject, 'lbl1') print("child: ", o) return 'a message from python' if __name__ == "__main__": app = QApplication(sys.argv) engine = QQmlApplicationEngine() ctx = engine.rootContext() engine.load('test.qml') win = engine.rootObjects()[0] py_mainapp = MainApp(ctx, win) ctx.setContextProperty("py_mainapp", py_mainapp) win.show() sys.exit(app.exec_())
Here is the qml file
import QtQuick 2.9 import QtQuick.Controls 1.4 Window { title: "Test" width: 400 height: 300 Label { id: lbl1 } Label { id: lbl2 } Button { text: 'pressme' onClicked: { lbl2.text = py_mainapp.foo('hello from qml') } } }
Does anyone knows what am I doing wrong?
Here are the logs:
file:///Users/fabrice/Svn/qt/nasmediasPY/test.qml:20: Error: Cannot assign [undefined] to QString
from qml: Hello from qml was received
child: None -
o = self.win.findChild(QObject, 'lbl1')
I know nothing about QML. But as per http://doc.qt.io/qt-5/qobject.html#findChild, isn't the first parameter supposed to be a string of the name of the object?? Is your
findChild()
function a special PyQt one? -
I guess it's some kind of binding, but I'm not sure. I'm going to try to remove the first parameter and test
with self.win.findChild('lbl1'), Thanks for the advice -
I confirm self.win.findChild needs the QObject first parameter
-
@le-fab.
OK. So I believe https://stackoverflow.com/questions/36273655/cannot-call-findchildqobject-child-on-qobject-returns-none answers your question from QML/PyQt? Or https://stackoverflow.com/questions/44816536/pyqt5-and-qml-integration-issue-with-findchild-returning-attributeerror. Maybe? :) -
Thanks for the links.
self.win.findChild works, with the attribute objectName defined in the qml
Know I have to find why the text sent from python isn't received in the qmlIf it can help here is the code to access a qml child's property:
@pyqtSlot(QVariant) def foo(self, aword): print('from qml: %s was received' % (aword)) o = self.win.findChild(QObject, 'pyLbl1') print('text from label', o.property("text")) return 'a message from python'
-
@le-fab.
This is all way beyond my ken! But is it to do with eitherpyqtProperty
ortoPyObject
? See http://pyqt.sourceforge.net/Docs/PyQt5/qt_properties.html, and https://wiki.python.org/moin/PyQt/Binding widget properties to Python variables where code claims:return type(self.findChild(QObject, objectName).property(propertyName).toPyObject())
? :)
-
I was reading something close to the solution you pointed: https://riverbankcomputing.com/pipermail/pyqt/2017-January/038497.html
I guess i'm going to use this kind of implementation to populate the qml.I guess we can mark this post as solved because even if I can't read the string in the qml from my foo method, I know how to access and edit the qml child's properties with the win.findChild and the QObject.setProperty methods
Thanks for the help
Here is my final test code where everything is working:
main.py:#!/usr/bin/env python # -*- coding: utf-8 -*- import sys from PyQt5.QtWidgets import QApplication from PyQt5.QtQml import QQmlApplicationEngine from PyQt5.QtCore import QObject, pyqtSlot, QVariant class MainApp(QObject): def __init__(self, context, parent=None): super(MainApp, self).__init__(parent) self.win = parent self.ctx = context @pyqtSlot(QVariant) def foo(self, aword): print('from qml: %s was received' % (aword)) o = self.win.findChild(QObject, 'pyLbl1') print('text from label', o.property("text")) o = self.win.findChild(QObject, 'pyLbl2') o.setProperty("text", "some text from python") if __name__ == "__main__": app = QApplication(sys.argv) engine = QQmlApplicationEngine() ctx = engine.rootContext() engine.load('test.qml') win = engine.rootObjects()[0] py_mainapp = MainApp(ctx, win) ctx.setContextProperty("py_mainapp", py_mainapp) win.show() sys.exit(app.exec_())
test.qml:
import QtQuick 2.4 import QtQuick.Controls 1.4 ApplicationWindow { title: "Test" width: 400 height: 300 Column { anchors.fill: parent spacing: 10 Label { id: lbl1 objectName: 'pyLbl1' text: "text in qml" } Label { id: lbl2 objectName: 'pyLbl2' text: "unset" } Button { text: 'pressme' onClicked: { py_mainapp.foo('Hello from qml') } } } }
I guess I could have used the same name for id and objectName (the ids aren't used in this example anyway)