PyQt, JavaScript and WorkerScript
-
I'm trying to work with WorkerScript that will call a class in Python, but the way I have it setup is giving me an error when I run it.
Here is what I have so far:
import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtQml import * from PyQt5.QtWidgets import * from PyQt5.QtQuick import * class test(QObject): @pyqtSlot() def printStatus(self): print("hello from main.py") def main(): app = QGuiApplication(sys.argv) engine = QQmlApplicationEngine() tryTest = test() engine.rootContext().setContextProperty('tryTest', tryTest) engine.load(QUrl("main.qml")) app.exec() if __name__ == "__main__": main()
workerscript.js
WorkerScript.onMessage = function() { prt() } function prt() { tryTest.printStatus(); }
Part of my QML file that sets up the WorkerScript
Timer { id: timer1 interval: 1000 repeat: true running: true onTriggered: { worker.sendMessage(); } } WorkerScript { id: worker source: "workerscript.js" }
When I execute main.py my UI loads fine, but then when the WorkerScript calls my js file I get the following error message:
workerscript.js:6: ReferenceError: tryTest is not defined
I'm assuming it's because workerscript.js doesn't know about my main.py. When I import workerscript.js into my QML file and call the function without using the WorkerScript it executes just fine. Any idea what I am doing wrong? I am using PyQt5 and Python 3.
-
I'm trying to work with WorkerScript that will call a class in Python, but the way I have it setup is giving me an error when I run it.
Here is what I have so far:
import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtQml import * from PyQt5.QtWidgets import * from PyQt5.QtQuick import * class test(QObject): @pyqtSlot() def printStatus(self): print("hello from main.py") def main(): app = QGuiApplication(sys.argv) engine = QQmlApplicationEngine() tryTest = test() engine.rootContext().setContextProperty('tryTest', tryTest) engine.load(QUrl("main.qml")) app.exec() if __name__ == "__main__": main()
workerscript.js
WorkerScript.onMessage = function() { prt() } function prt() { tryTest.printStatus(); }
Part of my QML file that sets up the WorkerScript
Timer { id: timer1 interval: 1000 repeat: true running: true onTriggered: { worker.sendMessage(); } } WorkerScript { id: worker source: "workerscript.js" }
When I execute main.py my UI loads fine, but then when the WorkerScript calls my js file I get the following error message:
workerscript.js:6: ReferenceError: tryTest is not defined
I'm assuming it's because workerscript.js doesn't know about my main.py. When I import workerscript.js into my QML file and call the function without using the WorkerScript it executes just fine. Any idea what I am doing wrong? I am using PyQt5 and Python 3.
@JasonS Hi! The JS file is executed on its own thread and in its own context. Thus it can't access anything from your QML engine's context, including the context properties you set. The only way to interact with the WorkerScript is using
sendMessage
and theonMessage
. -
@JasonS Hi! The JS file is executed on its own thread and in its own context. Thus it can't access anything from your QML engine's context, including the context properties you set. The only way to interact with the WorkerScript is using
sendMessage
and theonMessage
.@Wieland Hmm...I think I'm following you, but aren't I using sendMessage? I create the WorkerScript element as worker that has workerscript.js as the source file. In my onTriggered from my timer I do worker.sendMessage().
Timer { id: timer1 interval: 1000 repeat: true running: true onTriggered: { worker.sendMessage(); } } WorkerScript { id: worker source: "workerscript.js" }
Then what I'm trying to do is access my Python class from my JS file which is where I'm getting the error. It's not seeing the Python file even though the Python script is what loads the UI. Is this what you mean or are you saying what I am trying to do wont work? If it wont work do you have any suggestions on how I can access my class in the Python file using a different thread like the WorkerScript does? I need to interact with GPIO on my Raspberry PI which will take time based on the fact that I will be turning a motor on for an extended period of time. I don't want the GUI to lock up while this happens. I thought the WorkerScript would be perfect for this.
-
You're using
tryTest
in your JS file, but it's not available there. I guess what you actually want looks more like:workerscript.js
WorkerScript.onMessage = function(message) { // compute something based on message var z = message.x + message.y // send result WorkerScript.sendMessage({x:message.x, y:message.y, z:z}) }
main.qml
Timer { id: timer interval: 1000 repeat: true running: true property real x : 23 property real y : 42 onTriggered: worker.sendMessage({x:timer.x, y:timer.y}); } WorkerScript { id: worker source: "workerscript.js" onMessage: { tryTest.printStatus(); // <- that's OK console.log("z=x+y <=> "+ messageObject.z +"="+ messageObject.x +"+"+ messageObject.y ) } }
-
You're using
tryTest
in your JS file, but it's not available there. I guess what you actually want looks more like:workerscript.js
WorkerScript.onMessage = function(message) { // compute something based on message var z = message.x + message.y // send result WorkerScript.sendMessage({x:message.x, y:message.y, z:z}) }
main.qml
Timer { id: timer interval: 1000 repeat: true running: true property real x : 23 property real y : 42 onTriggered: worker.sendMessage({x:timer.x, y:timer.y}); } WorkerScript { id: worker source: "workerscript.js" onMessage: { tryTest.printStatus(); // <- that's OK console.log("z=x+y <=> "+ messageObject.z +"="+ messageObject.x +"+"+ messageObject.y ) } }
@Wieland Ok, I see the issue now and what the script you provided is doing. Basically it looks like you could put anything in the workerscript.js sendMessage just so it triggers the onMessage in the QML, then since the Python is hooked up to the QML it knows about tryTest. Correct? I think I proved this by passing "" to sendMessage... WorkerScript.sendMessage("").
Seems like a hokey way of doing it. Well, at least I know this works, but I think I'll look for a cleaner solution. I think I have a couple:
https://wiki.qt.io/Updating-QML-content-from-Python-threads
http://stackoverflow.com/questions/10650960/concurrent-programming-in-python-and-qtand this one gives multiple options:
http://stackoverflow.com/questions/6783194/background-thread-with-qthread-in-pyqtIf you have other ideas it would be super duper if you shared, otherwise I have some playing around to do.
Thanks for all your help.
-
Ah, sry, I was a bit out of it. Controlling your motor from a WorkerScript looks like a bad idea in general and I have doubts if it's even possible without some really dirty hacks. QtQuick is good for implementing GUIs but all business logic belongs in a C++ backend (or Python backend, if you like that better). QML is great for GUIs but its too limited to do any serious tasks beyond just that. Also JavaScript is really slow and I don't think you want to control real hardware with that.
-
@Wieland Ok, I see the issue now and what the script you provided is doing. Basically it looks like you could put anything in the workerscript.js sendMessage just so it triggers the onMessage in the QML, then since the Python is hooked up to the QML it knows about tryTest. Correct? I think I proved this by passing "" to sendMessage... WorkerScript.sendMessage("").
Seems like a hokey way of doing it. Well, at least I know this works, but I think I'll look for a cleaner solution. I think I have a couple:
https://wiki.qt.io/Updating-QML-content-from-Python-threads
http://stackoverflow.com/questions/10650960/concurrent-programming-in-python-and-qtand this one gives multiple options:
http://stackoverflow.com/questions/6783194/background-thread-with-qthread-in-pyqtIf you have other ideas it would be super duper if you shared, otherwise I have some playing around to do.
Thanks for all your help.
@JasonS said in PyQt, JavaScript and WorkerScript:
Correct?
Yes, that's how it works :) Also your idea of using an extra thread in Python to control the motor seems to be the best idea.