从QML,我想:
- 调用 Python 插槽。
- 传递回调。
- 在槽完成后运行该回调。
我试过这个:
- 注册上下文属性 (
Service
) - 呼叫
Service.request("data", function (response) { console.log(response) }
- 在 Python 中,该函数作为
QtQml.QJSValue
接收 - 该函数在经过一些昂贵的操作后在单独的线程中调用
但是,该函数仅在有时起作用,大多数时候根本没有效果或使Python解释器崩溃。如果我删除对time.sleep(1)
的调用,它更有可能产生结果。
有什么想法吗?
这是上述的非工作实现
主.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_()
应用.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");
}
实现是从这里
改编的https://github.com/ben-github/PyQt5-QML-CallbackFunction
最好
马库斯
我找到了一种也有效的替代方法。
区别在于:
- Python 注册一个新类型,而不是设置上下文属性
- Python不是从Python调用Javascript,而是发出信号
这对我来说似乎更干净,因为Javascript永远不必进入Python。
主.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_()
应用.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");
}
文档中没有迹象表明QJSValue
是线程安全的。此页指示可重入类或线程安全的类在文档中被标记为可重入类。但是,页面上没有提到QJSValue
的单词线程。
因此,我建议您确保仅从主线程调用回调。显然,你仍然希望将长时间运行的任务放在一个线程中,所以我建议使用类似QCoreApplication.postEvent()
的东西将事件从你的Python线程发送到主线程,然后主线程将调用你的回调函数。
注意:我已经在这里包装了对 PyQt4 QCoreApplication.postEvent
的调用。如果你需要帮助理解如何使用QCoreApplication.postEvent
方法,你也可以调整它以与 PyQt5 配合使用。