如何使用 QWebChannel 从 python 接收数据到 js?

Posted

技术标签:

【中文标题】如何使用 QWebChannel 从 python 接收数据到 js?【英文标题】:How to receive data from python to js using QWebChannel? 【发布时间】:2019-10-02 23:05:56 【问题描述】:

我正在尝试让我的 PyQt 应用程序与 JS 进行通信,但无法从 python 获取值。我在 python 端有两个插槽来获取和打印数据。在示例中,一个 int 从 JS 传递给 python,python 给它加 5 并传递回来,然后 JS 调用另一个 slot 打印新值。

var backend = null;
var x = 15;
new QWebChannel(qt.webChannelTransport, function (channel) 
    backend = channel.objects.backend;
    backend.getRef(x, function(pyval)
        backend.printRef(pyval)
    );
);
@pyqtSlot(int)
def getRef(self, x):
    print('inside getRef', x)
    return x + 5

@pyqtSlot(int)
def printRef(self, ref):
    print('inside printRef', ref)

输出:

inside getRef 15
Could not convert argument QJsonValue(null) to target type int .

预期:

inside getRef 15
inside printRef 20

我无法弄清楚为什么返回的值为空。我如何将该 pyval 存储到 js 端的变量中以供以后使用?

【问题讨论】:

def printRefisnt 返回任何东西。 没有任何数据应该从 printRef 传回。它只是打印从 getRef 接收到的值。问题是 getRef 没有将值 x+5 正确返回给 JS,因此它可以由 printRef 打印。 【参考方案1】:

在 C++ 中,为了使方法可以返回一个值,它必须声明为 Q_INVOKABLE,而在 PyQt 中的等价物是在 @pyqtSlot 装饰器中使用 result

├── index.html
└── main.py

ma​​in.py

from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebChannel


class Backend(QtCore.QObject):
    @QtCore.pyqtSlot(int, result=int)
    def getRef(self, x):
        print("inside getRef", x)
        return x + 5

    @QtCore.pyqtSlot(int)
    def printRef(self, ref):
        print("inside printRef", ref)


if __name__ == "__main__":
    import os
    import sys

    app = QtWidgets.QApplication(sys.argv)

    backend = Backend()

    view = QtWebEngineWidgets.QWebEngineView()

    channel = QtWebChannel.QWebChannel()
    view.page().setWebChannel(channel)
    channel.registerObject("backend", backend)

    current_dir = os.path.dirname(os.path.realpath(__file__))
    filename = os.path.join(current_dir, "index.html")
    url = QtCore.QUrl.fromLocalFile(filename)
    view.load(url)

    view.resize(640, 480)
    view.show()
    sys.exit(app.exec_())

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
        <script type="text/javascript">
            var backend = null;
            var x = 5;
            window.onload = function()
            
                new QWebChannel(qt.webChannelTransport, function(channel) 
                    backend = channel.objects.backend;
                    backend.getRef(x, function(pyval) 
                        backend.printRef(pyval);
                    );
                );
            
        </script>
    </head>
</html>

更新:

一般情况下,QtWebChannel只能传输Qt端可以转化为QJsonObject的信息,而javascript端只能传输可以转化为JSON的数据。

所以有一些特殊情况:

int 浮动 str 列表:是否支持发送和接收列表,但包含数字和字符串等元素,以及支持以前基本类型的字典和其他列表。
class Backend(QtCore.QObject):
    @QtCore.pyqtSlot(result=list)
    def return_list(self):
        return [0.0, 1.5, 'Hello', ['Stack', 5.0], 'a': 'b': 'c']

    @QtCore.pyqtSlot(list)
    def print_list(self, l):
        print(l)
backend = channel.objects.backend;
backend.return_list(function(pyval) 
    backend.print_list(pyval);
);

输出:

[0.0, 1.5, 'Hello', ['Stack', 5.0], 'a': 'b': 'c']
字典
class Backend(QtCore.QObject):
    @QtCore.pyqtSlot(result="QJsonObject")
    def return_dict(self):
        return "a": 1.5, "b": "c": 2, "d": [1, "3", "4"]

    @QtCore.pyqtSlot("QJsonObject")
    def print_dict(self, ref):
        print(ref)
backend = channel.objects.backend;
backend.return_dict(function(pyval) 
    backend.print_dict(pyval);
);

输出:

'a': <PyQt5.QtCore.QJsonValue object at 0x7f3841d50150>, 'b': <PyQt5.QtCore.QJsonValue object at 0x7f3841d501d0>, 'd': <PyQt5.QtCore.QJsonValue object at 0x7f3841d50250>

如您所见,返回的是 QJsonValue,因此获取信息可能很繁琐,因此解决方法是将它们打包在一个列表中:

class Backend(QtCore.QObject):
    @QtCore.pyqtSlot(result=list)
    def return_list(self):
        d = "a": 1.5, "b": "c": 2, "d": [1, "3", "4"]
        return [d]

    @QtCore.pyqtSlot(list)
    def print_list(self, ref):
        d, *_ = ref
        print(d)
backend = channel.objects.backend;
backend.return_list(function(pyval) 
    backend.print_list(pyval);
);

输出:

'a': 1.5, 'b': 'c': 2.0, 'd': [1.0, '3', '4']

更新2:

一种通用的信息传递方式是使用JSON,即将python或js对象转换成字符串,分别使用json.dumps()JSON.stringify(),然后发送;在 python 或 js 中接收到的字符串必须分别使用json.loads()JSON.parse() 进行转换:

class Backend(QtCore.QObject):
    @QtCore.pyqtSlot(str, result=str)
    def getRef(self, o):
        print("inside getRef", o)
        py_obj = json.loads(o)
        py_obj["c"] = ("Hello", "from", "Python")
        return json.dumps(py_obj)

    @QtCore.pyqtSlot(str)
    def printRef(self, o):
        py_obj = json.loads(o)
        print("inside printRef", py_obj)
var backend = null;
window.onload = function()

    new QWebChannel(qt.webChannelTransport, function(channel) 
        backend = channel.objects.backend;
        var x = a: "1000", b: ["Hello", "From", "JS"]
        backend.getRef(JSON.stringify(x), function(y) 
            js_obj = JSON.parse(y);
            js_obj["f"] = false;
            backend.printRef(JSON.stringify(js_obj));
        );
    );

inside getRef "a":"1000","b":["Hello","From","JS"]
inside printRef 'a': '1000', 'b': ['Hello', 'From', 'JS'], 'c': ['Hello', 'from', 'Python'], 'f': False

【讨论】:

dict和list结构也是这样吗? @eyllanesc 不确定这个问题是否超出了这里的范围,但我仍然可以问它......你如何从 python->js 发送字典?如果我有sendDict = pyqtSignal(dict) 然后我做talkie.sendDict.emit("a":10) 我会在js 方面得到null。另一方面,在处理int, float, str, bool, list 等简单类型时,js 端的值可以正常接收... 仅遇到list, set, tuple, object, ... 等数据类型的问题 @BPL 我添加了一个更通用的方法,通常应该允许发送任何可以转换为 JSON 的对象。 @eyllanesc 呵呵,很酷,当我几个小时前问你同样的问题时,我得出了与你相似的结论,当时我担心编码/解码的可能开销...我已经使用了一段时间,它似乎已经足够好了。就是说,非常感谢您确认这是发送这些数据类型的惯用方式...我已经为您投票了;)

以上是关于如何使用 QWebChannel 从 python 接收数据到 js?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Qt 中使用 QWebChannel 发送 QJsonObject

如何注册一个类以在 Qt 的 QWebChannel 信号中使用它

如何设置 QWebChannel JS API 以在 QWebEngineView 中使用?

Linux + Qt : QWebEngineView + QWebChannel 与 JS 交互传递信息

消除 QWebChannel 属性通知器信号警告

使用 QWebChannel 时未定义的属性和返回类型