如何传递要在 Pyside2 中调用的函数?
How to pass function to be invoked in Pyside2?
我正在尝试使用 runJavaScript 函数从 QWebEngineView 获取一些数据,但它出错并显示以下错误消息。
有办法解决吗?较旧的主题表明这是 Pyside2 中的限制,因此不确定现在是否已解决。
from PySide2 import QtCore, QtWidgets, QtGui, QtWebEngineWidgets
def callbackfunction(html):
print html
file = "myhtmlfile.html"
view = QtWebEngineWidgets.QWebEngineView()
view.load(QtCore.QUrl.fromLocalFile(file))
view.page().runJavaScript("document.getElementsByTagName('html')[0].innerHTML", callbackfunction)
TypeError: 'PySide2.QtWebEngineWidgets.QWebEnginePage.runJavaScript' called with wrong argument types:
PySide2.QtWebEngineWidgets.QWebEnginePage.runJavaScript(str, function)
Supported signatures:
PySide2.QtWebEngineWidgets.QWebEnginePage.runJavaScript(str)
PySide2.QtWebEngineWidgets.QWebEnginePage.runJavaScript(str, int)
PySide2 不提供 runJavaScript 的所有重载方法,因此不支持向其传递回调。一种可能的解决方法是使用 QtWebChannel,它通过 websockets 实现 javascript 和 python 之间的通信:
import sys
import os
from PySide2 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebChannel
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class Backend(QtCore.QObject):
htmlChanged = QtCore.Signal()
def __init__(self, parent=None):
super(Backend, self).__init__(parent)
self._html = ""
@QtCore.Slot(str)
def toHtml(self, html):
self._html = html
self.htmlChanged.emit()
@property
def html(self):
return self._html
class WebEnginePage(QtWebEngineWidgets.QWebEnginePage):
def __init__(self, parent=None):
super(WebEnginePage, self).__init__(parent)
self.loadFinished.connect(self.onLoadFinished)
self._backend = Backend()
self.backend.htmlChanged.connect(self.handle_htmlChanged)
@property
def backend(self):
return self._backend
@QtCore.Slot(bool)
def onLoadFinished(self, ok):
if ok:
self.load_qwebchannel()
self.load_object()
def load_qwebchannel(self):
file = QtCore.QFile(":/qtwebchannel/qwebchannel.js")
if file.open(QtCore.QIODevice.ReadOnly):
content = file.readAll()
file.close()
self.runJavaScript(content.data().decode())
if self.webChannel() is None:
channel = QtWebChannel.QWebChannel(self)
self.setWebChannel(channel)
def load_object(self):
if self.webChannel() is not None:
self.webChannel().registerObject("backend", self.backend)
script = r"""
new QWebChannel(qt.webChannelTransport, function (channel) {
var backend = channel.objects.backend;
var html = document.getElementsByTagName('html')[0].innerHTML;
backend.toHtml(html);
});"""
self.runJavaScript(script)
def handle_htmlChanged(self):
print(self.backend.html)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
filename = os.path.join(CURRENT_DIR, "index.html")
url = QtCore.QUrl.fromLocalFile(filename)
page = WebEnginePage()
view = QtWebEngineWidgets.QWebEngineView()
page.load(url)
view.setPage(page)
view.resize(640, 480)
view.show()
sys.exit(app.exec_())
我之前的逻辑只关注获取 HTML 但在这部分答案中我将尝试概括逻辑以便能够关联回调。这个想法是将响应发送到关联与回调相关的 uuid 的桥对象,消息必须以 json 格式发送才能处理不同类型的数据。
import json
import os
import sys
from PySide2 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebChannel
from jinja2 import Template
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class Bridge(QtCore.QObject):
initialized = QtCore.Signal()
def __init__(self, parent=None):
super().__init__(parent)
self._callbacks = dict()
@property
def callbacks(self):
return self._callbacks
@QtCore.Slot()
def init(self):
self.initialized.emit()
@QtCore.Slot(str, str)
def send(self, uuid, data):
res = json.loads(data)
callback = self.callbacks.pop(uuid, None)
if callable(callable):
callback(res)
class WebEnginePage(QtWebEngineWidgets.QWebEnginePage):
def __init__(self, parent=None):
super(WebEnginePage, self).__init__(parent)
self.loadFinished.connect(self.onLoadFinished)
self._bridge = Bridge()
@property
def bridge(self):
return self._bridge
@QtCore.Slot(bool)
def onLoadFinished(self, ok):
if ok:
self.load_qwebchannel()
self.load_object()
def load_qwebchannel(self):
file = QtCore.QFile(":/qtwebchannel/qwebchannel.js")
if file.open(QtCore.QIODevice.ReadOnly):
content = file.readAll()
file.close()
self.runJavaScript(content.data().decode())
if self.webChannel() is None:
channel = QtWebChannel.QWebChannel(self)
self.setWebChannel(channel)
def load_object(self):
if self.webChannel() is not None:
self.webChannel().registerObject("bridge", self.bridge)
script = r"""
var bridge = null;
new QWebChannel(qt.webChannelTransport, function (channel) {
bridge = channel.objects.bridge;
bridge.init();
});"""
self.runJavaScript(script)
def execute(self, code, callback, uuid=""):
uuid = uuid or QtCore.QUuid.createUuid().toString()
self.bridge.callbacks[uuid] = callback
script = Template(code).render(uuid=uuid)
self.runJavaScript(script)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.page = WebEnginePage()
self.view = QtWebEngineWidgets.QWebEngineView()
self.view.setPage(self.page)
self.page.bridge.initialized.connect(self.handle_initialized)
self.setCentralWidget(self.view)
filename = os.path.join(CURRENT_DIR, "index.html")
url = QtCore.QUrl.fromLocalFile(filename)
self.view.load(url)
def handle_initialized(self):
self.page.execute(
"""
var value = document.getElementsByTagName('html')[0].innerHTML
bridge.send('{{uuid}}', JSON.stringify(value));
""",
callbackfunction,
)
def callbackfunction(html):
print(html)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
我正在尝试使用 runJavaScript 函数从 QWebEngineView 获取一些数据,但它出错并显示以下错误消息。
有办法解决吗?较旧的主题表明这是 Pyside2 中的限制,因此不确定现在是否已解决。
from PySide2 import QtCore, QtWidgets, QtGui, QtWebEngineWidgets
def callbackfunction(html):
print html
file = "myhtmlfile.html"
view = QtWebEngineWidgets.QWebEngineView()
view.load(QtCore.QUrl.fromLocalFile(file))
view.page().runJavaScript("document.getElementsByTagName('html')[0].innerHTML", callbackfunction)
TypeError: 'PySide2.QtWebEngineWidgets.QWebEnginePage.runJavaScript' called with wrong argument types:
PySide2.QtWebEngineWidgets.QWebEnginePage.runJavaScript(str, function)
Supported signatures:
PySide2.QtWebEngineWidgets.QWebEnginePage.runJavaScript(str)
PySide2.QtWebEngineWidgets.QWebEnginePage.runJavaScript(str, int)
PySide2 不提供 runJavaScript 的所有重载方法,因此不支持向其传递回调。一种可能的解决方法是使用 QtWebChannel,它通过 websockets 实现 javascript 和 python 之间的通信:
import sys
import os
from PySide2 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebChannel
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class Backend(QtCore.QObject):
htmlChanged = QtCore.Signal()
def __init__(self, parent=None):
super(Backend, self).__init__(parent)
self._html = ""
@QtCore.Slot(str)
def toHtml(self, html):
self._html = html
self.htmlChanged.emit()
@property
def html(self):
return self._html
class WebEnginePage(QtWebEngineWidgets.QWebEnginePage):
def __init__(self, parent=None):
super(WebEnginePage, self).__init__(parent)
self.loadFinished.connect(self.onLoadFinished)
self._backend = Backend()
self.backend.htmlChanged.connect(self.handle_htmlChanged)
@property
def backend(self):
return self._backend
@QtCore.Slot(bool)
def onLoadFinished(self, ok):
if ok:
self.load_qwebchannel()
self.load_object()
def load_qwebchannel(self):
file = QtCore.QFile(":/qtwebchannel/qwebchannel.js")
if file.open(QtCore.QIODevice.ReadOnly):
content = file.readAll()
file.close()
self.runJavaScript(content.data().decode())
if self.webChannel() is None:
channel = QtWebChannel.QWebChannel(self)
self.setWebChannel(channel)
def load_object(self):
if self.webChannel() is not None:
self.webChannel().registerObject("backend", self.backend)
script = r"""
new QWebChannel(qt.webChannelTransport, function (channel) {
var backend = channel.objects.backend;
var html = document.getElementsByTagName('html')[0].innerHTML;
backend.toHtml(html);
});"""
self.runJavaScript(script)
def handle_htmlChanged(self):
print(self.backend.html)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
filename = os.path.join(CURRENT_DIR, "index.html")
url = QtCore.QUrl.fromLocalFile(filename)
page = WebEnginePage()
view = QtWebEngineWidgets.QWebEngineView()
page.load(url)
view.setPage(page)
view.resize(640, 480)
view.show()
sys.exit(app.exec_())
我之前的逻辑只关注获取 HTML 但在这部分答案中我将尝试概括逻辑以便能够关联回调。这个想法是将响应发送到关联与回调相关的 uuid 的桥对象,消息必须以 json 格式发送才能处理不同类型的数据。
import json
import os
import sys
from PySide2 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebChannel
from jinja2 import Template
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class Bridge(QtCore.QObject):
initialized = QtCore.Signal()
def __init__(self, parent=None):
super().__init__(parent)
self._callbacks = dict()
@property
def callbacks(self):
return self._callbacks
@QtCore.Slot()
def init(self):
self.initialized.emit()
@QtCore.Slot(str, str)
def send(self, uuid, data):
res = json.loads(data)
callback = self.callbacks.pop(uuid, None)
if callable(callable):
callback(res)
class WebEnginePage(QtWebEngineWidgets.QWebEnginePage):
def __init__(self, parent=None):
super(WebEnginePage, self).__init__(parent)
self.loadFinished.connect(self.onLoadFinished)
self._bridge = Bridge()
@property
def bridge(self):
return self._bridge
@QtCore.Slot(bool)
def onLoadFinished(self, ok):
if ok:
self.load_qwebchannel()
self.load_object()
def load_qwebchannel(self):
file = QtCore.QFile(":/qtwebchannel/qwebchannel.js")
if file.open(QtCore.QIODevice.ReadOnly):
content = file.readAll()
file.close()
self.runJavaScript(content.data().decode())
if self.webChannel() is None:
channel = QtWebChannel.QWebChannel(self)
self.setWebChannel(channel)
def load_object(self):
if self.webChannel() is not None:
self.webChannel().registerObject("bridge", self.bridge)
script = r"""
var bridge = null;
new QWebChannel(qt.webChannelTransport, function (channel) {
bridge = channel.objects.bridge;
bridge.init();
});"""
self.runJavaScript(script)
def execute(self, code, callback, uuid=""):
uuid = uuid or QtCore.QUuid.createUuid().toString()
self.bridge.callbacks[uuid] = callback
script = Template(code).render(uuid=uuid)
self.runJavaScript(script)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.page = WebEnginePage()
self.view = QtWebEngineWidgets.QWebEngineView()
self.view.setPage(self.page)
self.page.bridge.initialized.connect(self.handle_initialized)
self.setCentralWidget(self.view)
filename = os.path.join(CURRENT_DIR, "index.html")
url = QtCore.QUrl.fromLocalFile(filename)
self.view.load(url)
def handle_initialized(self):
self.page.execute(
"""
var value = document.getElementsByTagName('html')[0].innerHTML
bridge.send('{{uuid}}', JSON.stringify(value));
""",
callbackfunction,
)
def callbackfunction(html):
print(html)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())