python GUI库图形界面开发之PyQt5中QWebEngineView内嵌网页与Python的数据交互传参详细方法实例


Posted in Python onFebruary 26, 2020

这几天研究了下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-40021
        var 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 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;
        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 nodejs
if (typeof module === 'object') {
  module.exports = {
    QWebChannel: QWebChannel
  };
}

Python主函数代码

import sys
import Web_UI
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QObject,pyqtProperty
from PyQt5.QtWebChannel import QWebChannel
from MySharedObject import MySharedObject
from PyQt5.QtWidgets import QApplication
import os

BASE_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 QObject
from PyQt5.QtCore import pyqtProperty
from PyQt5.QtWidgets import QWidget,QMessageBox

class 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 = "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>

实现效果

python GUI库图形界面开发之PyQt5中QWebEngineView内嵌网页与Python的数据交互传参详细方法实例

python GUI库图形界面开发之PyQt5中QWebEngineView内嵌网页与Python的数据交互传参详细方法实例

本文详细介绍了PyQt5中使用QWebEngineView控件内嵌网页与Python的数据交互的方法与实例,更多关于这方面的知识请查看下面的相关链接

Python 相关文章推荐
浅谈Python NLP入门教程
Dec 25 Python
详谈pandas中agg函数和apply函数的区别
Apr 20 Python
对python使用http、https代理的实例讲解
May 07 Python
python安装twisted的问题解析
Aug 21 Python
Python面向对象之类和对象实例详解
Dec 10 Python
python中PS 图像调整算法原理之亮度调整
Jun 28 Python
python 实现创建文件夹和创建日志文件的方法
Jul 07 Python
python实现在多维数组中挑选符合条件的全部元素
Nov 26 Python
python3.6使用SMTP协议发送邮件
May 20 Python
详解python datetime模块
Aug 17 Python
Python函数__new__及__init__作用及区别解析
Aug 31 Python
Python面向对象特殊属性及方法解析
Sep 16 Python
python自动点赞功能的实现思路
Feb 26 #Python
python GUI库图形界面开发之PyQt5时间控件QTimer详细使用方法与实例
Feb 26 #Python
python GUI库图形界面开发之PyQt5窗口控件QWidget详细使用方法
Feb 26 #Python
python GUI库图形界面开发之PyQt5窗口类QMainWindow详细使用方法
Feb 26 #Python
python GUI库图形界面开发之PyQt5中QMainWindow, QWidget以及QDialog的区别和选择
Feb 26 #Python
python实现全排列代码(回溯、深度优先搜索)
Feb 26 #Python
python GUI库图形界面开发之PyQt5 Qt Designer工具(Qt设计师)详细使用方法及Designer ui文件转py文件方法
Feb 26 #Python
You might like
先进的自动咖啡技术,真的可以取代咖啡师吗?
2021/03/06 冲泡冲煮
Web程序工作原理详解
2014/12/25 PHP
PHP微信开发之模板消息回复
2016/06/24 PHP
PHP类和对象相关系统函数与运算符小结
2016/09/28 PHP
深入认识JavaScript中的函数
2007/01/22 Javascript
JavaScript 异步调用框架 (Part 1 - 问题 &amp; 场景)
2009/08/03 Javascript
jquery将一个表单序列化为一个对象的方法
2013/12/02 Javascript
nodejs中使用monk访问mongodb
2014/07/06 NodeJs
javascript ajax的5种状态介绍
2014/08/18 Javascript
通过jquery 获取URL参数并进行转码
2014/08/18 Javascript
js点击button按钮跳转到另一个新页面
2014/10/10 Javascript
jQuery实现淡入淡出二级下拉导航菜单的方法
2015/08/28 Javascript
网站发布后Bootstrap框架引用woff字体无法正常显示的解决方法
2016/11/24 Javascript
微信小程序 Button 组件详解及简单实例
2017/01/10 Javascript
微信小程序--组件(swiper)详细介绍
2017/06/13 Javascript
简单谈谈JS中的正则表达式
2017/09/11 Javascript
JS控制鼠标拒绝点击某一按钮的实例
2017/12/29 Javascript
Vue组件通信的四种方式汇总
2018/02/08 Javascript
jQuery的ztree仿windows文件新建和拖拽功能的实现代码
2018/12/05 jQuery
es6数组includes()用法实例分析
2020/04/18 Javascript
通过vue刷新左侧菜单栏操作
2020/08/06 Javascript
[44:51]2018DOTA2亚洲邀请赛 4.4 淘汰赛 VP vs Liquid 第二场
2018/04/05 DOTA
[01:02:09]Liquid vs TNC 2019国际邀请赛淘汰赛 胜者组 BO3 第二场 8.21
2020/07/19 DOTA
Python Web框架Flask信号机制(signals)介绍
2015/01/01 Python
python统计日志ip访问数的方法
2015/07/06 Python
python实现逻辑回归的方法示例
2017/05/02 Python
Python实现爬取需要登录的网站完整示例
2017/08/19 Python
Python常用类型转换实现代码实例
2020/07/28 Python
你的自行车健身专家:FaFit24
2016/11/16 全球购物
Bergfreunde丹麦:登山装备网上零售商
2017/02/26 全球购物
房地产广告策划方案
2014/05/15 职场文书
个人欠款担保书
2014/05/20 职场文书
授权委托书样本及填写说明
2014/09/19 职场文书
2014年最新个人对照检查材料范文
2014/09/25 职场文书
实用求职信模板范文
2019/05/13 职场文书
感恩信:写给爸爸妈妈的一封感谢信
2019/09/12 职场文书