PYQT 与 js 交互

作者: 时尚灬IT男 | 来源:发表于2019-03-05 16:58 被阅读5次

    PYQT做图形界面还是没有HTML做的炫,所以将PYQT与静态HTML结合就会有焕然一新的感觉。

    HTML文件

    <!DOCTYPE html>

    <title>QWebChannel交互Demo

    <button onclick="sendMes()">发送消息

    <p id="mes">

    <script type="text/javascript" src="qwebchannel.js">

        window.onload = function() {

    new QWebChannel(qt.webChannelTransport, function (channel) {

    window.printer= channel.objects.printer;  // 此处channel.objects.printer中的printer就是上文提到的功能类注册的标识名

    });

    };

    function sendMes() {  // 调用python端的功能类的方法执行操作

    // var a = printer.print('你收到一条网页发送的消息!')

    printer.print('你收到一条网页发送的消息!');

    }

    function uptext(msg) {

    alert(msg);

    }

    </html>

    这里需要加载qwebchannel.js,下面是qwebchannel.js文件

    /****************************************************************************

    **

    ** Copyright (C) 2016 The Qt Company Ltd.

    ** Copyright (C) 2014 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:BSD$

    ** 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.

    **

    ** BSD License Usage

    ** Alternatively, 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 iin 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 objectNamein data) {

    var object =new QObject(objectName, data[objectName], channel);

    }

    // now unwrap properties, which might reference other registered objects

            for (var objectNamein 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 (responseinstanceof 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 propertyNamein qObject) {

    propertyNames.push(propertyName);

    }

    for (var idxin 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 propertyIdxin 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 propertyIndexin propertyMap) {

    var propertyValue = propertyMap[propertyIndex];

    object.__propertyCache__[propertyIndex] = propertyValue;

    }

    for (var signalNamein 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 namein data.enums) {

    object[name] = data.enums[name];

    }

    }

    //required for use with nodejs

    if (typeof module ==='object') {

    module.exports = {

    QWebChannel: QWebChannel

    };

    }

    对应的python文件

    #! /usr/bin/env

    # -*- coding: utf-8 -*-

    # webview.py

    from PyQt5.QtWidgetsimport QApplication

    from PyQt5.QtCoreimport QObject, pyqtSlot, QUrl

    from PyQt5.QtWebChannelimport QWebChannel

    from PyQt5.QtWebEngineWidgetsimport QWebEngineView

    import sys

    class Print(QObject):

    # pyqtSlot,中文网络上大多称其为槽。作用是接收网页发起的信号

        @pyqtSlot(str, result=str)

    def print(self, content):

    print(content)# 对接收到的内容进行处理,比如调用打印机进行打印等等。此处略去,只在bash中显示接收到的消息

            self.myHello()

    return 'python'

        @pyqtSlot(result=str)

    def myHello(self):

    browser.page().runJavaScript('uptext("hello, Python");')

    # print('call received')

            return 'hello, Python'

    if __name__ =='__main__':

    app = QApplication(sys.argv)

    browser = QWebEngineView()# 新增一个浏览器引擎

        browser.setWindowTitle('QWebChannel交互Demo')

    browser.resize(900, 600)

    channel = QWebChannel()# 增加一个通信中需要用到的频道

        printer = Print()# 通信过程中需要使用到的功能类

        channel.registerObject('printer', printer)# 将功能类注册到频道中,注册名可以任意,但将在网页中作为标识

        browser.page().setWebChannel(channel)# 在浏览器中设置该频道

        url_string ="file:///D:/wb/PycharmProjects/wb/index.html"  # 内置的网页地址,此处我采用的是本地的。远程同样可以使用。

        browser.load(QUrl(url_string))

    browser.show()

    sys.exit(app.exec_())

    效果图是下面:

    做这个demo的目的是为了与echarts结合做除完美的数据分析显示界面。

    相关文章

      网友评论

        本文标题:PYQT 与 js 交互

        本文链接:https://www.haomeiwen.com/subject/uneouqtx.html