【问题标题】:How to pass function to be invoked in Pyside2?如何传递要在 Pyside2 中调用的函数?
【发布时间】:2020-10-04 15:00:37
【问题描述】:

我正在尝试使用 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)

【问题讨论】:

    标签: python python-2.7 qt pyside2 qwebengineview


    【解决方案1】:

    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_())
    

    【讨论】:

    • 谢谢,这非常有用。但我不明白 load_object 函数。这只是硬编码以通过标签名称获取元素吗?所以我可以改变它来运行任何javascript?即使我不需要传递回调函数?
    • @JoanVenge 1) 我建议您阅读有关 QtWebChannel 的内容,2) 简单来说,QtWebChannel 实现了 python 和 javascript 之间的桥梁。 QtWebChannel 映射 QObject 的属性并将它们发送到 js,以便它创建具有这些属性的对象,因此将 QObject 注册为“后端”,然后通过“channel.objects.backend”获取另一个对象,但具有映射的属性,然后当使用映射对象的任何方法(如 toHtml)时,它会将信息发送到 QObject 方法,这是通过 websockets 完成的。
    • @JoanVenge 3) 我的回答专门回答了你的问题并且没有超越它,不要夸大我的回答。在您的特定情况下,您想在 python 中获得document.getElementsByTagName('html')[0].innerHTML 的结果,仅此一项就是我的答案。我不是想实现一个通用的功能。
    • @JoanVenge 4) 我认为您应该等我回答您的其他问题,因为我将向您展示如何与 javascript 交互来操作属性,但我仍在努力完善我的解决方案
    • 谢谢,但是在您的代码运行后,它应该打印 innerHtml 吗?因为我没有打印出来,所以我尝试了 page.load_object(),但它是一样的。调用 page.handle_htmlChanged 我得到的对象没有属性 _html。
    猜你喜欢
    • 1970-01-01
    • 2019-04-19
    • 1970-01-01
    • 2021-04-30
    • 1970-01-01
    • 2022-01-12
    • 2013-07-05
    • 2021-04-28
    • 1970-01-01
    相关资源
    最近更新 更多