PyQt WebEngineView interferes with MainMenu

I'm trying to create an application that contains a web browser within it, but when I add the web browser my menu bar visually disappears but functionally remains in place. The following are two images, one showing the "self.centralWidget(self.web_widget)" commented out, and the other allows that line to run. If you run the example code, you will also see that while visually the entire web page appears as if the menu bar wasn't present, you have to click slightly below each entry field and button in order to activate it, behaving as if the menu bar was in fact present.

继续阅读PyQt WebEngineView interferes with MainMenu

macOS Catalina(10.15.4)下PyQt5图标Icon无法显示的问题

macOS下,刚学习PyQt5遇到图标无法显示问题. 先上代码,看下代码就明白了。

import sys
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtCore import QFile
 
 
# 面向对象编程
class main_widget(QWidget):  # 继承自 QWidget类
    def __init__(self):
        super().__init__()
        self.initUI()  # 创建窗口
 
 
    def initUI(self):  #  在此处添加 窗口控件
        self.setGeometry(300, 200, 600, 500)  # 屏幕上坐标(x, y), 和 窗口大小(宽,高)
        self.setWindowTitle("第一个qt5窗口")
        # self.setWindowIcon(QIcon("logo/m4.png"))  # 设置窗口图标
        self.show()
 
 
if __name__ == "__main__":
    app = QApplication(sys.argv)
    path = 'logo/m4.png'
    print(QFile.exists(path))
    app.setWindowIcon(QIcon(path))  # MAC 下 程序图标是显示在程序坞中的, 切记;
    window = main_widget()
    sys.exit(app.exec())  # 退出主循环

setWindowIconQApplication的方法,而不是QWidget的,所以使用app.setWindowIcon设置是对的。

注意在macOS下,图标是显示在程序坞中的!!!

参考链接


MAC下PyQt5图标Icon无法显示的问题

Python+WebKit+HTML开发桌面应用程序

前言

几天前写了一个备份的小工具,用Python写个脚本,也就花个一天的时间搞定,给客户用了一下,对功能很满意,但对界面不满足,想要一个图形界面来管理;
一个备份脚本,有必要整成这样子吗?没办法,谁让是上帝的要求呢,就研究一下;先后找了python的tkinter、pyqt,尝试着画一些界面,虽然功能可以实现,但界面很难看;恰好,在查看pyqt的API文档的时候 ,发现了QWebView组件,据介绍可以实现一个浏览器,并且浏览器中的JS可以与Python代码进行交互。忽然眼前一亮,我不正是我想要的吗!
抽几天时间把工具写完了,目前运行良好;想写篇博客做个记录,也想给需要进行此类开发的朋友做个示例,就把这个工具最核心的内容做了一个DEMO。现在,就把这个小DEMO的开发过程做个说明;

功能简介

这个小DEMO用于显示计算机上的一些信息,显示内容不是主要的,主要为了体现如何用python+HTML开发桌面应用程序。

1.使用HTML开发一个界面;
2.在页面上显示主机信息;
3.实现功能时,体现JS调用Python和Python调用JS;

最终实现的主界面如下:

准备材料

开发工具:Visual Studio Code
开发语言:python 2.7/python 3.7
界面工具包:PyQT4/PyQT5

目录结构


说明:
demoUI.py : python主体程序 ,主要实现窗口创建,加载index.html页面,python与JS的交互;
views/index.html : 主要进行数据展示,及与python交互的js程序

demoUI.py开发

引入的包

from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4 import QtWebKit
from PyQt4 import QtNetwork

#处理中文问题
import sys,json
from PyQt4.Qt import QObject
reload(sys) 
sys.setdefaultencoding('utf8')

前四行代码,主要是引入Qt相关的包,这里使用的PyQt4;
后三行代码,主要是为了处理中文问题,可以忽略,但遇到中文乱码的时候,想起来这句话,把它加上就行了

DemoWin类

class DemoWin(QtWebKit.QWebView):

    def __init__(self):
        QtWebKit.QWebView.__init__(self)
        self.resize(800, 600)
        self.setUrl(QtCore.QUrl('views/index.html'))
        self.show()
        mainFrame = self.page().mainFrame()
        winobj = WinObj(mainFrame)
        mainFrame.javaScriptWindowObjectCleared.connect(lambda: mainFrame.addToJavaScriptWindowObject(QtCore.QString('WinObj'), winobj)) 

DemoWin是整个应用的核心类,主要实现窗体创建,关联与JS交互的槽函数等;

class DemoWin(QtWebKit.QWebView):

像标题所说,我们将使用WebKit作为页面展示,所以这里的主页面是以QtWebKit.QWebView作为基类

QtWebKit.QWebView.init(self)
self.resize(800, 600)
self.setUrl(QtCore.QUrl(‘views/index.html’))
self.show()

设置窗口大小为800*600,并且加载index.html进行显示

mainFrame = self.page().mainFrame()

获取网页主框架,按照QT官方文档解释:QWebFrame代表一个网页框架,每一个QWebFrame至少包含一个主框架,使用QWebFrame.mainFrame()获取。
这是进行后续各类操作的基础,类似于JS中只有获取到网页的dom对象,才可以对其中的元素操作一样;

winobj = WinObj(mainFrame)
mainFrame.javaScriptWindowObjectCleared.connect(lambda: mainFrame.addToJavaScriptWindowObject(QtCore.QString(‘WinObj’), winobj)) ##js调用python

这段代码是关键中的关键!
WinObj类:是封装后用于js调用的槽函数类,后续再详细介绍
addToJavaScriptWindowObject类:第一个参数是对象在javascript里的名字, 可以自由命名, 第二个参数是对应的QObject实例指针。 这样在javascript里就可以直接访问WinObj对象拉, 是不是看上去超级简单?但是这个函数的调用时机是有讲究的,按照官方文档的推荐,是需要在javaScriptWindowObjectCleared信号的槽里调用,才有了以上的代码;里面用了lambda表达式,纯粹是为了减少一个槽函数的定义,你如果觉得不好看或不喜欢,完全可以定义一个槽函数;

WinObj类

下面来重点介绍WinObj类,类型定义如下:

class WinObj(QtCore.QObject):
    def __init__(self,mainFrame):
        super(WinObj,self).__init__()
        self.mainFrame = mainFrame

    @QtCore.pyqtSlot(result="QString")
    def getInfo(self):
        import socket,platform
        hostname = socket.gethostname()
        try:
            ip = socket.gethostbyname(hostname)
        except:
            ip = ''
        list_info = platform.uname()
        sys_name = list_info[0] + list_info[2]
        cpu_name = list_info[5]
        dic_info = {"hostname":hostname,"ip":ip,"sys_name":sys_name, \
                    "cpu_name":cpu_name}
        #调用js函数,实现回调
        self.mainFrame.evaluateJavaScript('%s(%s)' % ('onGetInfo',json.dumps(dic_info)))
        return json.dumps(dic_info)

该类封装了JS可直接调用的方法,有一些区别于普通类的地方要注意;

class WinObj(QtCore.QObject):

该类必须继承自QObject,而不能是object。

def __init__(self,mainFrame):
        super(WinObj,self).__init__()
        self.mainFrame = mainFrame

构造函数将mainFrame传进来,主要用于调用js函数(后面将有示例介绍)

    @QtCore.pyqtSlot(result="QString")
    def getInfo(self):
        import socket,platform
        hostname = socket.gethostname()
        ip = socket.gethostbyname(hostname)
        list_info = platform.uname()
        sys_name = list_info[0] + list_info[2]
        cpu_name = list_info[5]
        dic_info = {"hostname":hostname,"ip":ip,"sys_name":sys_name, \
                    "cpu_name":cpu_name}
        #调用js函数,实现回调
        self.mainFrame.evaluateJavaScript('%s(%s)' % ('onGetInfo',json.dumps(dic_info)))
        return json.dumps(dic_info)

getInfo()实现了一个供JS调用的方法,有几下几点要注意:
(1)@QtCore.pyqtSlot(result=”QString”) 用于将python方法转换为供js用的函数,括号里写明数据类型及返回类型;如果没有声明result,则不能返回数据;
(2)self.mainFrame.evaluateJavaScript(‘%s(%s)’ % (‘onGetInfo’,json.dumps(dic_info)))
调用页面中用JS声明的onGetInfo函数(这里仅作一个示例,将查询到的数据进行回调返回)

demoUI.py完整代码下:

#coding=utf-8
'''
Created on 2017年11月3日

@author: Administrator
'''
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4 import QtWebKit
from PyQt4 import QtNetwork

#处理中文问题
import sys,json
from PyQt4.Qt import QObject
reload(sys)  
sys.setdefaultencoding('utf8')

class DemoWin(QtWebKit.QWebView):

    def __init__(self):
        QtWebKit.QWebView.__init__(self)
        self.resize(800, 600)
        self.setUrl(QtCore.QUrl('views/index.html'))
        self.show()
        mainFrame = self.page().mainFrame()
        winobj = WinObj(mainFrame)
        mainFrame.javaScriptWindowObjectCleared.connect(lambda: mainFrame.addToJavaScriptWindowObject(QtCore.QString('WinObj'), winobj)) ##js调用python

class WinObj(QtCore.QObject):
    def __init__(self,mainFrame):
        super(WinObj,self).__init__()
        self.mainFrame = mainFrame

    @QtCore.pyqtSlot(result="QString")
    def getInfo(self):
        import socket,platform
        hostname = socket.gethostname()
        try:
            ip = socket.gethostbyname(hostname)
        except:
            ip = ''
        list_info = platform.uname()
        sys_name = list_info[0] + list_info[2]
        cpu_name = list_info[5]
        dic_info = {"hostname":hostname,"ip":ip,"sys_name":sys_name, \
                    "cpu_name":cpu_name}
        #调用js函数,实现回调
        self.mainFrame.evaluateJavaScript('%s(%s)' % ('onGetInfo',json.dumps(dic_info)))
        return json.dumps(dic_info)

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    demoWin = DemoWin()
    sys.exit(app.exec_())

index.html

<!DOCTYPE script PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src='https://www.mobibrw.com/wp-content/uploads/2020/05/vue.min_.1.0.11.js'></script>
</head>
<body>

<div id='app'>
<input type='button' onclick='getInfo()' value="获取机器信息">
<H3 >主机名:{{config.hostname}} </H3>
<H3 >IP:{{config.ip}} </H3>
<H3 >操作系统:{{config.sys_name}} </H3>
<H3 >CPU:{{config.cpu_name}} </H3>
<H3>以下为python回调信息:</H3>
<label id='info'></label>
</div>
</body>
</html>
<script type="text/javascript">
var vm = new Vue({
    el: '#app',
    data:{
        config:{}
    },
    created:function(){
        this.config = JSON.parse(window.WinObj.getInfo())
    }
});

function getInfo() {
    info = JSON.parse(window.WinObj.getInfo())
    alert(info)
}

//python回调的js函数
function onGetInfo(info) {
    document.getElementById('info').innerText=JSON.stringify(info)
}

</script>

Python 3.7/PyQT5下需要安装 pyQt5

$ python3 -m pip install --upgrade pip

$ pip3 install SIP

$ pip3 install pyQt5

$ pip3 install --upgrade PyQt5

$ pip3 install PyQtWebEngine

Python 3.7/PyQT5下的代码(注意,Python 2.7/PyQT4是的交互是同步调用的,但是Python 3.7/PyQT5通过QtWebChannel交互是异步的)如下:

#coding=utf-8
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5 import QtWidgets
from PyQt5 import QtWebEngineWidgets
from PyQt5 import QtNetwork
from PyQt5 import QtWebChannel

#处理中文问题
import sys,json
from PyQt5.Qt import QObject
#reload(sys)  
#sys.setdefaultencoding('utf8')

# 调试窗口配置
# 如果不想自己创建调试窗口,可以使用Chrome连接这个地址进行调试
import os
DEBUG_PORT = '5588'
DEBUG_URL = 'http://127.0.0.1:%s' % DEBUG_PORT
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = DEBUG_PORT

def qt_message_handler(mode, context, message):
    if mode == QtCore.QtInfoMsg:
        mode = 'INFO'
    elif mode == QtCore.QtWarningMsg:
        mode = 'WARNING'
    elif mode == QtCore.QtCriticalMsg:
        mode = 'CRITICAL'
    elif mode == QtCore.QtFatalMsg:
        mode = 'FATAL'
    else:
        mode = 'DEBUG'
    print('qt_message_handler: line: %d, func: %s(), file: %s' % (
          context.line, context.function, context.file))
    print('  %s: %s\n' % (mode, message))

QtCore.qInstallMessageHandler(qt_message_handler)

class DemoWin(QtWebEngineWidgets.QWebEngineView):

    def __init__(self):
        QtWebEngineWidgets.QWebEngineView.__init__(self)
        self.setWindowTitle('Web页面中的JavaScript与 QWebEngineView交互例子')
        self.resize(800, 600)
        
        self.settings().setAttribute(QtWebEngineWidgets.QWebEngineSettings.PluginsEnabled, True)
        self.settings().setAttribute(QtWebEngineWidgets.QWebEngineSettings.JavascriptEnabled, True)
        
        url = QtCore.QUrl('file:///' + QtCore.QFileInfo('views/index.html').absoluteFilePath())
        #url = QtCore.QUrl("https://www.baidu.com")
        self.setUrl(url)
        self.show()
        
        self.winobj = WinObj(self.page())
        self.channel = QtWebChannel.QWebChannel()
        self.channel.registerObject("con", self.winobj)
        self.page().setWebChannel(self.channel)
        
        #网页调试窗口
        self.inspector = QtWebEngineWidgets.QWebEngineView()
        self.inspector.setWindowTitle('Web Inspector')
        self.inspector.load(QtCore.QUrl(DEBUG_URL))
        self.loadFinished.connect(self.handleHtmlLoaded)
        
    def handleHtmlLoaded(self, ok):
        if ok:
            self.page().setDevToolsPage(self.inspector.page())
            self.inspector.show()

class WinObj(QtCore.QObject):
    def __init__(self, page):
        super(WinObj,self).__init__()
        self.page = page

    @QtCore.pyqtSlot(result="QString")
    def getInfo(self):
        import socket,platform
        hostname = socket.gethostname()
        try:
            ip = socket.gethostbyname(hostname)
        except:
            ip = ''
        list_info = platform.uname()
        sys_name = list_info[0] + list_info[2]
        cpu_name = list_info[5]
        dic_info = {"hostname":hostname,"ip":ip,"sys_name":sys_name, \
                    "cpu_name":cpu_name}
        #调用js函数,实现回调
        self.page.runJavaScript('%s(%s)' % ('onGetInfo',json.dumps(dic_info)))
        js = json.dumps(dic_info)
        return js

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    demoWin = DemoWin()
    sys.exit(app.exec())

index.html

<!DOCTYPE script PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="qwebchannel.js"></script>
<script type="text/javascript" src='https://www.mobibrw.com/wp-content/uploads/2020/05/vue.min_.1.0.11.js'></script>
</head>
<body>
<div id='app'>
<input type='button' onclick='getInfo()' value="获取机器信息">
<H3 >主机名:{{config.hostname}} </H3>
<H3 >IP:{{config.ip}} </H3>
<H3 >操作系统:{{config.sys_name}} </H3>
<H3 >CPU:{{config.cpu_name}} </H3>
<H3>以下为python回调信息:</H3>
<label id='info'></label>
</div>
</body>
</html>

<script type="text/javascript">
    new QWebChannel( qt.webChannelTransport, function(channel) {
            window.con = channel.objects.con;
            window.vm = new Vue({
                el: '#app',
                data: {
                    config:{}
                },
                created:function() {
                    window.con.getInfo(function(info) {
                    window.vm.config = JSON.parse(info);
                });
            }
        });
    });

    function getInfo() {
        window.con.getInfo();
    }

    //python回调的js函数
    function onGetInfo(info) {
        document.getElementById('info').innerText=JSON.stringify(info)
    }
</script>

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 <milian.wolff@kdab.com>
** 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 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
    };
}

参考链接


PyQt5出现ImportError cannot import name 'QtWebEngineWidgets' from 'PyQt5' 问题解决

今天想在macOS Catalina(10.15.4)系统上,测试一下Python下的QT界面操作,结果在执行

from PyQt5 import QtWebEngineWidgets

的时候,报告如下错误:

Exception has occurred: ImportError
cannot import name 'QtWebEngineWidgets' from 'PyQt5' (/usr/local/lib/python3.7/site-packages/PyQt5/__init__.py)
  File "demoUI.py", line 10, in <module>
    from PyQt5 import QtWebEngineWidgets

也有可能报告:

No module named PyQt5.QtWebEngineWidgets

查了好久才在Stack Overflow上找到一个回答,说是这个模块被PyQt5移除了,需要单独安装。

安装命令为:

$ pip install PyQtWebEngine

参考链接


Python-PyQt5-PyQtWebEngine采坑记录-- No module named PyQt5.QtWebEngineWidgets

PyQt5出现No module named 'PyQt5.sip'问题解决

今天想在macOS Catalina(10.15.4)系统上,测试一下Python下的QT界面操作,结果在执行

from PyQt5 import QtCore

的时候报告错误:

No module named 'PyQt5.sip'

解决方法如下:

$ python3 -m pip install --upgrade pip

$ pip3 install SIP

$ pip3 install pyQt5

$ pip3 install --upgrade PyQt5

$ pip3 install PyQtWebEngine

参考链接


No module named 'pip._internal.cli.main'

今天,在macOS Catalina (10.15.4)系统上执行升级pip的命令

$ pip3 install --upgrade pip

之后,执行更新命令,报告错误。如下:

$ pip3 install --upgrade PyQt5
Traceback (most recent call last):
  File "/usr/local/bin/pip3", line 5, in <module>
    from pip._internal.cli.main import main
ModuleNotFoundError: No module named 'pip._internal.cli.main'

解决方法为重新升级安装一次 pip,如下:

$ python3 -m pip install --upgrade pip

参考链接


解决 ModuleNotFoundError: No module named 'pip._internal'

pip批量更新过期的python库

今天看了下系统环境,不少python库都有了更新,再用旧版本库可能已经不适合了,就想把所有的库都更新到最新版本。

查看系统里过期的python库,可以用pip命令

$ pip list #列出所有安装的库
$ pip list --outdated #列出所有过期的库

对于列出的过期库,pip也提供了更新的命令

$ pip install --upgrade 库名

但此命令不支持全局全部库升级。

在stackoverflow上有人提供了批量更新的办法,一个循环就搞定(注意--upgrade后面的空格)

import pip
from subprocess import call

for dist in pip.get_installed_distributions():
    call("pip install --upgrade " + dist.project_name, shell=True)

另外的也有人提到用 pip-review ,不想安装就没用

$ pip install pip-review

$ pip-review --local --interactive --auto

参考链接


pip --upgrade批量更新过期的python库

openpyxl - A Python library to read/write Excel 2010 xlsx/xlsm files

$ pip install openpyxl
from openpyxl import Workbook
wb = Workbook()

# grab the active worksheet
ws = wb.active

# Data can be assigned directly to cells
ws['A1'] = 42

# Rows can also be appended
ws.append([1, 2, 3])

# Python types will automatically be converted
import datetime
ws['A2'] = datetime.datetime.now()

# Save the file
wb.save("sample.xlsx")

openpyxl特点

  openpyxl(可读写excel表)专门处理Excel2007及以上版本产生的xlsx文件,xls和xlsx之间转换容易 注意:如果文字编码是“gb2312” 读取后就会显示乱码,请先转成Unicode

继续阅读openpyxl - A Python library to read/write Excel 2010 xlsx/xlsm files

华为手机配置显示返回键

使用华为Honor V8习惯了Android屏幕最下方的三个操作按键(返回/Home/列表),三个按键所在的位置被称之为"导航栏"。

最近换了华为Honor 30,想要点返回键时,却发现手机屏幕上没有返回键。手势操作非常不方便,经常误操作。而且有些界面适配的很不好,界面上没有设置回退功能。当缺少系统层面的返回按键的时候,只能强制退出应用。

其实这个返回键是在导航键里,需要设置才会显示。下面几个步骤就教你如何设置返回键:

继续阅读华为手机配置显示返回键

Gradle: 一个诡异的问题(ERROR: Failed to parse XML AndroidManifest.xml ParseError at [row,col]:[5,5] Message: expected start or end tag)

今天同事说他下了一个老版本的Android Studio项目死活编不过,我心想不就是一个项目么,编不过要么就是代码有问题,要么就是依赖库不完整这能有什么问题,于是自己在自己电脑试了下,结果自己也中招了:

继续阅读Gradle: 一个诡异的问题(ERROR: Failed to parse XML AndroidManifest.xml ParseError at [row,col]:[5,5] Message: expected start or end tag)