Reputation: 3301
From QML, I'd like to:
I've tried this:
Service
)Service.request("data", function (response) { console.log(response) }
QtQml.QJSValue
However, the function only has an effect sometimes, and most of the time not at all or crashes the Python interpreter. If I remove the call to time.sleep(1)
, it is more likely to produce results.
Any ideas?
Here's a non-working implementation of the above
main.qml
import QtQuick 2.3
import "application.js" as App
Rectangle {
id: appWindow
width: 200
height: 200
Component.onCompleted: App.onLoad()
}
main.py
import sys
import time
import threading
from PyQt5 import QtCore, QtGui, QtQml, QtQuick
class Service(QtCore.QObject):
def __init__(self, parent=None):
super(Service, self).__init__(parent)
@QtCore.pyqtSlot(str, str, QtCore.QVariant, QtQml.QJSValue)
def request(self, verb, endpoint, data, cb):
"""Expensive call"""
print verb, endpoint, data
self.cb = cb
def thread():
time.sleep(1)
event = QtCore.QEvent(1000)
event.return_value = "expensive result"
QtGui.QGuiApplication.postEvent(self, event)
worker = threading.Thread(target=thread)
worker.daemon = False
worker.start()
self.worker = worker
def event(self, event):
if event.type() == 1000:
self.cb.call([event.return_value])
return super(Service, self).event(event)
app = QtGui.QGuiApplication(sys.argv)
view = QtQuick.QQuickView()
context = view.rootContext()
service = Service()
context.setContextProperty("Service", service)
view.setSource(QtCore.QUrl("main.qml"))
view.show()
app.exec_()
application.js
"use strict";
/*global print, Service*/
function onLoad() {
Service.request("POST", "/endpoint", {"data": "value"}, function (reply) {
print(reply);
print(reply);
print(reply);
});
print("request() was made");
}
The implementation is adapted from here
https://github.com/ben-github/PyQt5-QML-CallbackFunction
Best,
Marcus
Upvotes: 1
Views: 2014
Reputation: 3301
I found an alternative approach that also works.
The differences are:
This seems cleaner to me, as Javascript never has to enter Python.
main.qml
import QtQuick 2.0
import "application.js" as App
Rectangle {
id: appWindow
width: 200
height: 200
Component.onCompleted: App.onLoad()
}
main.py
import sys
import time
import threading
from PyQt5 import QtCore, QtGui, QtQml, QtQuick
class MockHTTPRequest(QtCore.QObject):
requested = QtCore.pyqtSignal(QtCore.QVariant)
@QtCore.pyqtSlot(str, str, QtCore.QVariant)
def request(self, verb, endpoint, data):
"""Expensive call"""
print verb, endpoint, data
def thread():
time.sleep(1)
self.requested.emit("expensive result")
threading.Thread(target=thread).start()
app = QtGui.QGuiApplication(sys.argv)
view = QtQuick.QQuickView()
context = view.rootContext()
QtQml.qmlRegisterType(MockHTTPRequest, 'Service', 1, 0, 'MockHTTPRequest')
view.setSource(QtCore.QUrl("main.qml"))
view.show()
app.exec_()
application.js
"use strict";
/*global print, Service, Qt, appWindow*/
function MockHTTPRequest() {
return Qt.createQmlObject("import Service 1.0; MockHTTPRequest {}",
appWindow, "MockHTTPRequest");
}
function onLoad() {
var xhr = new MockHTTPRequest();
xhr.requested.connect(function (reply) {
print(reply);
});
xhr.request("POST", "/endpoint", {"data": "value"});
print("request() was made");
}
Upvotes: 2
Reputation: 11869
There is no indication from the documentation that QJSValue
is thread safe. This page indicates the classes that are re-entrant or thread safe are marked as such in the documentation. However, there is no mention of the word thread on the page for QJSValue
.
As such, I would suggest you make sure that your callback is only called from the main thread. Obviously, you are still going to want to put your long running task in a thread, so I would suggest using something like QCoreApplication.postEvent()
to send an event from your Python thread to the main thread, which will then call your callback function.
Note: I've wrapped calls to QCoreApplication.postEvent
for PyQt4 here. If you need help understanding how to use the QCoreApplication.postEvent
method, you can probably adapt it to work with PyQt5 as well.
Upvotes: 1