python GUI库图形界面开发之PyQt5中QWebEngineView内嵌网页与Python的数据交互传参详细方法实例
这几天研究了下PyQt5中QWebEngineView内嵌网页与Python的数据交互,今天把实例方法与代码发布出来供大家参数
数据交互需要load进一个网页,这里我选择load进一个本地html网页:JSTest.html。
同时,QWebEngineView与外面的交互还需要Qt官方提供的一个js文件:qwebchannel.js,这个文件可以在网上下载。
JSTest.html和qwebchannel.js两个文件放在同一个目录下,我这边都是放在Python工程目录下。
qwebchannel.js:
/******************************************************************************** Copyright (C) 2016 The Qt Company Ltd.** Contact: http://www.qt.io/licensing/**** This file is part of the examples of the Qt Toolkit.**** $QT_BEGIN_LICENSE:BSD$** You may use this file under the terms of the BSD license as follows:**** 'Redistribution and use in source and binary forms, with or without** modification, are permitted provided that the following conditions are** met:** * Redistributions of source code must retain the above copyright** notice, this list of conditions and the following disclaimer.** * Redistributions in binary form must reproduce the above copyright** notice, this list of conditions and the following disclaimer in** the documentation and/or other materials provided with the** distribution.** * Neither the name of The Qt Company Ltd nor the names of its** contributors may be used to endorse or promote products derived** from this software without specific prior written permission.****** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS** 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.'**** $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) { for (var i in message.data) { var data = message.data[i]; 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 (var objectName in data) { var object = new QObject(objectName, data[objectName], channel); } // now unwrap properties, which might reference other registered objects for (var objectName in 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 var ret = new Array(response.length); for (var i = 0; i < response.length; ++i) {ret[i] = object.unwrapQObject(response[i]); } return ret; } if (!response || !response['__QObject*__'] || response.id === undefined) { return response; } 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-40021var propertyNames = [];for (var propertyName in qObject) { propertyNames.push(propertyName);}for (var idx in propertyNames) { delete qObject[propertyNames[idx]];} } }); // here we are already initialized, and thus must directly unwrap the properties qObject.unwrapProperties(); return qObject; } this.unwrapProperties = function() { for (var propertyIdx in 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); if (!isPropertyNotifySignal && signalName !== 'destroyed') { // only required for 'pure' signals, handled separately for properties in propertyUpdate // also note that we always get notified about the destroyed signal 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 (var propertyIndex in propertyMap) { var propertyValue = propertyMap[propertyIndex]; object.__propertyCache__[propertyIndex] = propertyValue; } for (var signalName in 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, signalArgs); } function addMethod(methodData) { var methodName = methodData[0]; var methodIdx = methodData[1]; object[methodName] = function() { var args = []; var callback; for (var i = 0; i < arguments.length; ++i) {if (typeof arguments[i] === 'function') callback = arguments[i];else args.push(arguments[i]); } webChannel.exec({'type': QWebChannelMessageTypes.invokeMethod,'object': object.__id__,'method': methodIdx,'args': args }, function(response) {if (response !== undefined) { var result = object.unwrapQObject(response); if (callback) { (callback)(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 namenotifySignalData[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;webChannel.exec({ 'type': QWebChannelMessageTypes.setProperty, 'object': object.__id__, 'property': propertyIndex, 'value': value}); } }); } // ---------------------------------------------------------------------- data.methods.forEach(addMethod); data.properties.forEach(bindGetterSetter); data.signals.forEach(function(signal) { addSignal(signal, false); }); for (var name in data.enums) { object[name] = data.enums[name]; }} //required for use with nodejsif (typeof module === ’object’) { module.exports = { QWebChannel: QWebChannel };}
Python主函数代码
import sysimport Web_UIfrom PyQt5 import QtCore, QtGui, QtWidgetsfrom PyQt5.QtCore import QObject,pyqtPropertyfrom PyQt5.QtWebChannel import QWebChannelfrom MySharedObject import MySharedObjectfrom PyQt5.QtWidgets import QApplicationimport osBASE_DIR = os.path.dirname(__file__)#获取当前文件夹的绝对路径if __name__ == ’__main__’: app = QtWidgets.QApplication(sys.argv) MainWindow = QtWidgets.QFrame() ui = Web_UI.Ui_Form() ui.setupUi(MainWindow) ui.webView1.load(QtCore.QUrl(r''+BASE_DIR+'/JSTest.html')) channel = QWebChannel() ##创建一个QwebChannel对象,用于传递PyQt的参数到JavaScript myObj = MySharedObject() print(myObj.strval) channel.registerObject(’bridge’, myObj) ui.webView1.page().setWebChannel(channel) MainWindow.show() sys.exit(app.exec_())
继承了QWidget类的MySharedObject(Python文件)
from PyQt5.QtCore import QObjectfrom PyQt5.QtCore import pyqtPropertyfrom PyQt5.QtWidgets import QWidget,QMessageBoxclass MySharedObject(QWidget): def __init__(self,strval = ’100’): super(MySharedObject,self).__init__() self.strval = strval def _getStrValue(self): return self.strval def _setStrValue(self,str): self.strval = str print(’获得页面参数:%s’% str) QMessageBox.information(self,'Infomation', ’获得的页面参数%s’ % str) #需要定义的对外发布的方法 strValue= pyqtProperty(str,_getStrValue,_setStrValue)
页面代码HTML
<!DOCTYPE html> <html> <head><meta charset='UTF-8'><title>A Demo Page</title><script src = 'https://www.haobala.com/bcjs/qwebchannel.js'></script><script language = 'javascript'> function completeAndReturnName(){ var fname = document.getElementById(’fname’).value; var lname = document.getElementById(’lname’).value; var fullname = fname +’ ’+ lname; document.getElementById(’fullname’).value = fullname; document.getElementById(’submit-btn’).style.display = ’block’; return fullname; } document.addEventListener('DOMContentLoaded',function(){ new QWebChannel(qt.webChannelTransport, function (channel) { //alert(’111 chanel=’+channel) window.bridge = channel.objects.bridge; alert(’bridge=’+bridge.strValue +’n从pyqt传来的参数=’+window.bridge.strValue); //alert(’111 chanel=’+ channel.objects.strValue) }) } ) function onShowMsgBox() { if(window.bridge != null){ var fname = document.getElementById(’fname’).value; window.bridge.strValue = fname; //调用对象 } }</script> </head> <body><form> <label id = 'name'>User Name:</label> <input type = 'text' name = 'fname' id = 'fname'></input> <br /> <input type = 'button' value='传递参数到pyqt' οnclick='onShowMsgBox()'></input> <br /> <input type = 'reset' value='重置'></input></form> </body> </html>
实现效果
本文详细介绍了PyQt5中使用QWebEngineView控件内嵌网页与Python的数据交互的方法与实例,更多关于这方面的知识请查看下面的相关链接
相关文章: