From 4aa9ad36fc3604f15b7ce40bc3c9bb658bde899d Mon Sep 17 00:00:00 2001
From: Irony <892768447@qq.com>
Date: Thu, 16 Dec 2021 02:48:35 +0800
Subject: [PATCH] CallEachWithJs
---
QWebChannel/CallEachWithJs.py | 61 +++
QWebChannel/Data/CallEachWithJs.html | 76 ++++
QWebChannel/Data/qwebchannel.js | 448 ++++++++++++++++++++++
QWebChannel/Lib/WebChannelObject.py | 195 ++++++++++
QWebChannel/Lib/__init__.py | 0
QWebChannel/README.en.md | 0
QWebChannel/README.md | 12 +
QWebChannel/ScreenShot/CallEachWithJs.gif | Bin 0 -> 895006 bytes
README.md | 2 +
9 files changed, 794 insertions(+)
create mode 100644 QWebChannel/CallEachWithJs.py
create mode 100644 QWebChannel/Data/CallEachWithJs.html
create mode 100644 QWebChannel/Data/qwebchannel.js
create mode 100644 QWebChannel/Lib/WebChannelObject.py
create mode 100644 QWebChannel/Lib/__init__.py
create mode 100644 QWebChannel/README.en.md
create mode 100644 QWebChannel/README.md
create mode 100644 QWebChannel/ScreenShot/CallEachWithJs.gif
diff --git a/QWebChannel/CallEachWithJs.py b/QWebChannel/CallEachWithJs.py
new file mode 100644
index 0000000..238b062
--- /dev/null
+++ b/QWebChannel/CallEachWithJs.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2021/12/15
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: CallEachWithJs.py
+@description: 与JS之间的互相调用
+"""
+
+import os
+
+from PyQt5.QtCore import QUrl, pyqtSlot
+from PyQt5.QtGui import QDesktopServices
+from PyQt5.QtWidgets import (QApplication, QLineEdit, QPushButton, QVBoxLayout,
+ QWidget)
+
+from Lib.WebChannelObject import WebChannelObject
+
+
+class Window(QWidget):
+
+ def __init__(self, *args, **kwargs):
+ super(Window, self).__init__(*args, **kwargs)
+ self.m_obj = WebChannelObject(self)
+ # 注册该窗口,可以访问该窗口的属性,槽函数,信号
+ # https://doc.qt.io/qt-5/qwidget.html#properties
+ # https://doc.qt.io/qt-5/qwidget.html#signals
+ # https://doc.qt.io/qt-5/qwidget.html#public-slots
+ self.m_obj.registerObject('qtwindow', self)
+ self.m_obj.start()
+
+ layout = QVBoxLayout(self)
+ self.editTitle = QLineEdit(self, placeholderText='输入标题')
+ layout.addWidget(self.editTitle)
+ layout.addWidget(QPushButton('修改标题', self, clicked=self.onChangeTitle))
+
+ QDesktopServices.openUrl(
+ QUrl.fromLocalFile(
+ os.path.join(os.path.dirname(sys.argv[0] or __file__),
+ 'Data/CallEachWithJs.html')))
+
+ def onChangeTitle(self):
+ self.setWindowTitle(self.editTitle.text())
+
+ # ------- 把非槽函数通过pyqtSlot重新暴露 -------
+ @pyqtSlot(int, int)
+ def resize(self, width, height):
+ super().resize(width, height)
+
+
+if __name__ == '__main__':
+ import cgitb
+ import sys
+
+ cgitb.enable(format='text')
+ app = QApplication(sys.argv)
+ w = Window()
+ w.show()
+ sys.exit(app.exec_())
diff --git a/QWebChannel/Data/CallEachWithJs.html b/QWebChannel/Data/CallEachWithJs.html
new file mode 100644
index 0000000..fb5752b
--- /dev/null
+++ b/QWebChannel/Data/CallEachWithJs.html
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+ 输出:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/QWebChannel/Data/qwebchannel.js b/QWebChannel/Data/qwebchannel.js
new file mode 100644
index 0000000..32ad51e
--- /dev/null
+++ b/QWebChannel/Data/qwebchannel.js
@@ -0,0 +1,448 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtWebChannel module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+"use strict";
+
+var QWebChannelMessageTypes = {
+ signal: 1,
+ propertyUpdate: 2,
+ init: 3,
+ idle: 4,
+ debug: 5,
+ invokeMethod: 6,
+ connectToSignal: 7,
+ disconnectFromSignal: 8,
+ setProperty: 9,
+ response: 10,
+};
+
+var QWebChannel = function(transport, initCallback)
+{
+ if (typeof transport !== "object" || typeof transport.send !== "function") {
+ console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." +
+ " Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send));
+ return;
+ }
+
+ var channel = this;
+ this.transport = transport;
+
+ this.send = function(data)
+ {
+ if (typeof(data) !== "string") {
+ data = JSON.stringify(data);
+ }
+ channel.transport.send(data);
+ }
+
+ this.transport.onmessage = function(message)
+ {
+ var data = message.data;
+ if (typeof data === "string") {
+ data = JSON.parse(data);
+ }
+ switch (data.type) {
+ case QWebChannelMessageTypes.signal:
+ channel.handleSignal(data);
+ break;
+ case QWebChannelMessageTypes.response:
+ channel.handleResponse(data);
+ break;
+ case QWebChannelMessageTypes.propertyUpdate:
+ channel.handlePropertyUpdate(data);
+ break;
+ default:
+ console.error("invalid message received:", message.data);
+ break;
+ }
+ }
+
+ this.execCallbacks = {};
+ this.execId = 0;
+ this.exec = function(data, callback)
+ {
+ if (!callback) {
+ // if no callback is given, send directly
+ channel.send(data);
+ return;
+ }
+ if (channel.execId === Number.MAX_VALUE) {
+ // wrap
+ channel.execId = Number.MIN_VALUE;
+ }
+ if (data.hasOwnProperty("id")) {
+ console.error("Cannot exec message with property id: " + JSON.stringify(data));
+ return;
+ }
+ data.id = channel.execId++;
+ channel.execCallbacks[data.id] = callback;
+ channel.send(data);
+ };
+
+ this.objects = {};
+
+ this.handleSignal = function(message)
+ {
+ var object = channel.objects[message.object];
+ if (object) {
+ object.signalEmitted(message.signal, message.args);
+ } else {
+ console.warn("Unhandled signal: " + message.object + "::" + message.signal);
+ }
+ }
+
+ this.handleResponse = function(message)
+ {
+ if (!message.hasOwnProperty("id")) {
+ console.error("Invalid response message received: ", JSON.stringify(message));
+ return;
+ }
+ channel.execCallbacks[message.id](message.data);
+ delete channel.execCallbacks[message.id];
+ }
+
+ this.handlePropertyUpdate = function(message)
+ {
+ message.data.forEach(data => {
+ var object = channel.objects[data.object];
+ if (object) {
+ object.propertyUpdate(data.signals, data.properties);
+ } else {
+ console.warn("Unhandled property update: " + data.object + "::" + data.signal);
+ }
+ });
+ channel.exec({type: QWebChannelMessageTypes.idle});
+ }
+
+ this.debug = function(message)
+ {
+ channel.send({type: QWebChannelMessageTypes.debug, data: message});
+ };
+
+ channel.exec({type: QWebChannelMessageTypes.init}, function(data) {
+ for (const objectName of Object.keys(data)) {
+ new QObject(objectName, data[objectName], channel);
+ }
+
+ // now unwrap properties, which might reference other registered objects
+ for (const objectName of Object.keys(channel.objects)) {
+ channel.objects[objectName].unwrapProperties();
+ }
+
+ if (initCallback) {
+ initCallback(channel);
+ }
+ channel.exec({type: QWebChannelMessageTypes.idle});
+ });
+};
+
+function QObject(name, data, webChannel)
+{
+ this.__id__ = name;
+ webChannel.objects[name] = this;
+
+ // List of callbacks that get invoked upon signal emission
+ this.__objectSignals__ = {};
+
+ // Cache of all properties, updated when a notify signal is emitted
+ this.__propertyCache__ = {};
+
+ var object = this;
+
+ // ----------------------------------------------------------------------
+
+ this.unwrapQObject = function(response)
+ {
+ if (response instanceof Array) {
+ // support list of objects
+ return response.map(qobj => object.unwrapQObject(qobj))
+ }
+ if (!(response instanceof Object))
+ return response;
+
+ if (!response["__QObject*__"] || response.id === undefined) {
+ var jObj = {};
+ for (const propName of Object.keys(response)) {
+ jObj[propName] = object.unwrapQObject(response[propName]);
+ }
+ return jObj;
+ }
+
+ var objectId = response.id;
+ if (webChannel.objects[objectId])
+ return webChannel.objects[objectId];
+
+ if (!response.data) {
+ console.error("Cannot unwrap unknown QObject " + objectId + " without data.");
+ return;
+ }
+
+ var qObject = new QObject( objectId, response.data, webChannel );
+ qObject.destroyed.connect(function() {
+ if (webChannel.objects[objectId] === qObject) {
+ delete webChannel.objects[objectId];
+ // reset the now deleted QObject to an empty {} object
+ // just assigning {} though would not have the desired effect, but the
+ // below also ensures all external references will see the empty map
+ // NOTE: this detour is necessary to workaround QTBUG-40021
+ Object.keys(qObject).forEach(name => delete qObject[name]);
+ }
+ });
+ // here we are already initialized, and thus must directly unwrap the properties
+ qObject.unwrapProperties();
+ return qObject;
+ }
+
+ this.unwrapProperties = function()
+ {
+ for (const propertyIdx of Object.keys(object.__propertyCache__)) {
+ object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]);
+ }
+ }
+
+ function addSignal(signalData, isPropertyNotifySignal)
+ {
+ var signalName = signalData[0];
+ var signalIndex = signalData[1];
+ object[signalName] = {
+ connect: function(callback) {
+ if (typeof(callback) !== "function") {
+ console.error("Bad callback given to connect to signal " + signalName);
+ return;
+ }
+
+ object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || [];
+ object.__objectSignals__[signalIndex].push(callback);
+
+ // only required for "pure" signals, handled separately for properties in propertyUpdate
+ if (isPropertyNotifySignal)
+ return;
+
+ // also note that we always get notified about the destroyed signal
+ if (signalName === "destroyed" || signalName === "destroyed()" || signalName === "destroyed(QObject*)")
+ return;
+
+ // and otherwise we only need to be connected only once
+ if (object.__objectSignals__[signalIndex].length == 1) {
+ webChannel.exec({
+ type: QWebChannelMessageTypes.connectToSignal,
+ object: object.__id__,
+ signal: signalIndex
+ });
+ }
+ },
+ disconnect: function(callback) {
+ if (typeof(callback) !== "function") {
+ console.error("Bad callback given to disconnect from signal " + signalName);
+ return;
+ }
+ object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || [];
+ var idx = object.__objectSignals__[signalIndex].indexOf(callback);
+ if (idx === -1) {
+ console.error("Cannot find connection of signal " + signalName + " to " + callback.name);
+ return;
+ }
+ object.__objectSignals__[signalIndex].splice(idx, 1);
+ if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) {
+ // only required for "pure" signals, handled separately for properties in propertyUpdate
+ webChannel.exec({
+ type: QWebChannelMessageTypes.disconnectFromSignal,
+ object: object.__id__,
+ signal: signalIndex
+ });
+ }
+ }
+ };
+ }
+
+ /**
+ * Invokes all callbacks for the given signalname. Also works for property notify callbacks.
+ */
+ function invokeSignalCallbacks(signalName, signalArgs)
+ {
+ var connections = object.__objectSignals__[signalName];
+ if (connections) {
+ connections.forEach(function(callback) {
+ callback.apply(callback, signalArgs);
+ });
+ }
+ }
+
+ this.propertyUpdate = function(signals, propertyMap)
+ {
+ // update property cache
+ for (const propertyIndex of Object.keys(propertyMap)) {
+ var propertyValue = propertyMap[propertyIndex];
+ object.__propertyCache__[propertyIndex] = this.unwrapQObject(propertyValue);
+ }
+
+ for (const signalName of Object.keys(signals)) {
+ // Invoke all callbacks, as signalEmitted() does not. This ensures the
+ // property cache is updated before the callbacks are invoked.
+ invokeSignalCallbacks(signalName, signals[signalName]);
+ }
+ }
+
+ this.signalEmitted = function(signalName, signalArgs)
+ {
+ invokeSignalCallbacks(signalName, this.unwrapQObject(signalArgs));
+ }
+
+ function addMethod(methodData)
+ {
+ var methodName = methodData[0];
+ var methodIdx = methodData[1];
+
+ // Fully specified methods are invoked by id, others by name for host-side overload resolution
+ var invokedMethod = methodName[methodName.length - 1] === ')' ? methodIdx : methodName
+
+ object[methodName] = function() {
+ var args = [];
+ var callback;
+ var errCallback;
+ for (var i = 0; i < arguments.length; ++i) {
+ var argument = arguments[i];
+ if (typeof argument === "function")
+ callback = argument;
+ else if (argument instanceof QObject && webChannel.objects[argument.__id__] !== undefined)
+ args.push({
+ "id": argument.__id__
+ });
+ else
+ args.push(argument);
+ }
+
+ var result;
+ // during test, webChannel.exec synchronously calls the callback
+ // therefore, the promise must be constucted before calling
+ // webChannel.exec to ensure the callback is set up
+ if (!callback && (typeof(Promise) === 'function')) {
+ result = new Promise(function(resolve, reject) {
+ callback = resolve;
+ errCallback = reject;
+ });
+ }
+
+ webChannel.exec({
+ "type": QWebChannelMessageTypes.invokeMethod,
+ "object": object.__id__,
+ "method": invokedMethod,
+ "args": args
+ }, function(response) {
+ if (response !== undefined) {
+ var result = object.unwrapQObject(response);
+ if (callback) {
+ (callback)(result);
+ }
+ } else if (errCallback) {
+ (errCallback)();
+ }
+ });
+
+ return result;
+ };
+ }
+
+ function bindGetterSetter(propertyInfo)
+ {
+ var propertyIndex = propertyInfo[0];
+ var propertyName = propertyInfo[1];
+ var notifySignalData = propertyInfo[2];
+ // initialize property cache with current value
+ // NOTE: if this is an object, it is not directly unwrapped as it might
+ // reference other QObject that we do not know yet
+ object.__propertyCache__[propertyIndex] = propertyInfo[3];
+
+ if (notifySignalData) {
+ if (notifySignalData[0] === 1) {
+ // signal name is optimized away, reconstruct the actual name
+ notifySignalData[0] = propertyName + "Changed";
+ }
+ addSignal(notifySignalData, true);
+ }
+
+ Object.defineProperty(object, propertyName, {
+ configurable: true,
+ get: function () {
+ var propertyValue = object.__propertyCache__[propertyIndex];
+ if (propertyValue === undefined) {
+ // This shouldn't happen
+ console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__);
+ }
+
+ return propertyValue;
+ },
+ set: function(value) {
+ if (value === undefined) {
+ console.warn("Property setter for " + propertyName + " called with undefined value!");
+ return;
+ }
+ object.__propertyCache__[propertyIndex] = value;
+ var valueToSend = value;
+ if (valueToSend instanceof QObject && webChannel.objects[valueToSend.__id__] !== undefined)
+ valueToSend = { "id": valueToSend.__id__ };
+ webChannel.exec({
+ "type": QWebChannelMessageTypes.setProperty,
+ "object": object.__id__,
+ "property": propertyIndex,
+ "value": valueToSend
+ });
+ }
+ });
+
+ }
+
+ // ----------------------------------------------------------------------
+
+ data.methods.forEach(addMethod);
+
+ data.properties.forEach(bindGetterSetter);
+
+ data.signals.forEach(function(signal) { addSignal(signal, false); });
+
+ Object.assign(object, data.enums);
+}
+
+//required for use with nodejs
+if (typeof module === 'object') {
+ module.exports = {
+ QWebChannel: QWebChannel
+ };
+}
diff --git a/QWebChannel/Lib/WebChannelObject.py b/QWebChannel/Lib/WebChannelObject.py
new file mode 100644
index 0000000..7fe5e9e
--- /dev/null
+++ b/QWebChannel/Lib/WebChannelObject.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Created on 2021/12/15
+@author: Irony
+@site: https://pyqt.site , https://github.com/PyQt5
+@email: 892768447@qq.com
+@file: WebChannelObject.py
+@description: 交互对象,需要继承QObject并暴露接口
+"""
+
+from PyQt5.QtCore import (QJsonDocument, QJsonParseError, QObject,
+ pyqtProperty, pyqtSlot)
+from PyQt5.QtNetwork import QHostAddress
+from PyQt5.QtWebChannel import QWebChannel, QWebChannelAbstractTransport
+from PyQt5.QtWebSockets import QWebSocketServer
+
+
+class WebSocketTransport(QWebChannelAbstractTransport):
+
+ def __init__(self, socket, *args, **kwargs):
+ super(WebSocketTransport, self).__init__(*args, **kwargs)
+ self.m_socket = socket
+ self.m_socket.textMessageReceived.connect(self.textMessageReceived)
+ self.m_socket.disconnected.connect(self.deleteLater)
+
+ def sendMessage(self, message):
+ print('sendMessage:', message)
+ self.m_socket.sendTextMessage(
+ QJsonDocument(message).toJson(QJsonDocument.Compact).data().decode(
+ 'utf-8', errors='ignore'))
+
+ def textMessageReceived(self, message):
+ print('textMessageReceived:', message)
+ error = QJsonParseError()
+ json = QJsonDocument.fromJson(message.encode('utf-8', errors='ignore'),
+ error)
+ if error.error:
+ print('Failed to parse message:{}, Error is:{}'.format(
+ message, error.errorString()))
+ return
+ if not json.isObject():
+ print('Received JSON message that is not an object:{}'.format(
+ message))
+ return
+ self.messageReceived.emit(json.object(), self)
+
+
+class WebChannelObject(QObject):
+
+ def __init__(self, *args, **kwargs):
+ super(WebChannelObject, self).__init__(*args, **kwargs)
+ # 内部属性供外部调用
+ self._intValue = 0
+ self._floatValue = 0.0
+ self._boolValue = False
+ self._strValue = ''
+ # 设置数组或者字典有一定问题
+ # self._listValue = []
+ # self._mapValue = {}
+
+ # webchannel对象
+ self.m_webchannel = QWebChannel(self)
+ # 这里默认注册自己,这里使用了类名作为名称
+ self.registerObject(self.__class__.__name__, self)
+ # websocket服务
+ self.m_clients = {}
+ self.m_server = QWebSocketServer(self.__class__.__name__,
+ QWebSocketServer.NonSecureMode, self)
+
+ def registerObject(self, name, obj):
+ """注册对象
+ @param name: 名称
+ @type name: str
+ @param obj: 对象
+ @type obj: QObject
+ """
+ self.m_webchannel.registerObject(name, obj)
+
+ def registerObjects(self, objects):
+ """注册多个对象
+ @param objects: 对象列表
+ @type objects: list
+ """
+ for name, obj in objects:
+ self.registerObject(name, obj)
+
+ def deregisterObject(self, obj):
+ """注销对象
+ @param obj: 对象
+ @type obj: QObject
+ """
+ self.m_webchannel.deregisterObject(obj)
+
+ def deregisterObjects(self, objects):
+ """注销多个对象
+ @param objects: 对象列表
+ @type objects: list
+ """
+ for obj in objects:
+ self.deregisterObject(obj)
+
+ def start(self, port=12345):
+ """启动服务
+ @param port: 端口
+ @type port: int
+ """
+ if not self.m_server.listen(QHostAddress.Any, port):
+ raise Exception(
+ 'Failed to create WebSocket server on port {}'.format(port))
+
+ print('WebSocket server listening on port {}'.format(port))
+ # 新连接信号
+ self.m_server.newConnection.connect(self._handleNewConnection)
+
+ def stop(self):
+ """停止服务"""
+ self.m_server.close()
+
+ def _handleNewConnection(self):
+ """新连接"""
+ socket = self.m_server.nextPendingConnection()
+ print('New WebSocket connection from {}'.format(
+ socket.peerAddress().toString()))
+ # 连接关闭信号
+ socket.disconnected.connect(self._handleDisconnected)
+ transport = WebSocketTransport(socket)
+ self.m_clients[socket] = transport
+ self.m_webchannel.connectTo(transport)
+
+ def _handleDisconnected(self):
+ """连接关闭"""
+ socket = self.sender()
+ print('WebSocket connection from {} closed'.format(
+ socket.peerAddress()))
+ if socket in self.m_clients:
+ self.m_clients.pop(socket)
+ socket.deleteLater()
+
+ # ------- 下面是注册属性的方法 -------
+
+ @pyqtProperty(int)
+ def intValue(self):
+ return self._intValue
+
+ @intValue.setter
+ def intValue(self, value):
+ self._intValue = value
+
+ @pyqtProperty(float)
+ def floatValue(self):
+ return self._floatValue
+
+ @floatValue.setter
+ def floatValue(self, value):
+ self._floatValue = value
+
+ @pyqtProperty(bool)
+ def boolValue(self):
+ return self._boolValue
+
+ @boolValue.setter
+ def boolValue(self, value):
+ self._boolValue = value
+
+ @pyqtProperty(str)
+ def strValue(self):
+ return self._strValue
+
+ @strValue.setter
+ def strValue(self, value):
+ self._strValue = value
+
+ # @pyqtProperty(list)
+ # def listValue(self):
+ # return self._listValue
+
+ # @listValue.setter
+ # def listValue(self, value):
+ # self._listValue = value
+
+ # @pyqtProperty(dict)
+ # def mapValue(self):
+ # return self._mapValue
+
+ # @mapValue.setter
+ # def mapValue(self, value):
+ # self._mapValue = value
+
+ # ------- 下面是注册函数的方法 -------
+ # ------- 如果有返回值一定要注明 result=返回类型 -------
+
+ @pyqtSlot(int, int, result=int)
+ def testAdd(self, a, b):
+ return a + b
diff --git a/QWebChannel/Lib/__init__.py b/QWebChannel/Lib/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/QWebChannel/README.en.md b/QWebChannel/README.en.md
new file mode 100644
index 0000000..e69de29
diff --git a/QWebChannel/README.md b/QWebChannel/README.md
new file mode 100644
index 0000000..51618de
--- /dev/null
+++ b/QWebChannel/README.md
@@ -0,0 +1,12 @@
+# QWebChannel
+
+- 目录
+ - [和Js互相调用](#1和Js互相调用)
+
+## 1、和Js互相调用
+[运行 CallEachWithJs.py](CallEachWithJs.py)
+
+通过`qwebchannel.js`和`QWebChannel.registerObject`通过中间件`WebSocket`进行对象和Javascript的交互(类似于json rpc)
+该方法类似与`QWebEngineView`中的例子,同时该demo也适用与nodejs。
+
+![CallEachWithJs](ScreenShot/CallEachWithJs.gif)
\ No newline at end of file
diff --git a/QWebChannel/ScreenShot/CallEachWithJs.gif b/QWebChannel/ScreenShot/CallEachWithJs.gif
new file mode 100644
index 0000000000000000000000000000000000000000..8ff3a88e3c72509139b877dbdb047d597c5fe090
GIT binary patch
literal 895006
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