(详细分析)python PyQt5图形界面编程(含pyqtgraph画3D散点图、父子窗口间传递信息、pyinstaller打包可执行exe文件)

    技术2023-05-09  76

    目录

    介绍

     layout

    制作应用程序,pyinstaller

    单选框

    tab页中布局

    显示样式

    画3D散点图

    子窗口与父窗口信息交互


    介绍

    网站http://www.python3.vip/tut/py/gui/qt_09/Tkinter、wxPython、PySide2、PyQt5按住Ctrl拖动控件,直接复制

     layout

    sizepolicy控件大小按住Ctrl键拖动就是复制调整layout中控件的大小比例,优先使用layout的layoutStrentch属性来控制把控件挤小,可使用horizontal spacer

    制作应用程序,pyinstaller

    打包完之后会有两个文件夹build和dist,build不用管,dist里的是你需要的exe文件及相关东西。注意运行的时候,若代码编写时使用的静态加载(load)方法加载的UI文件,使用pyinstaller打包完后,需要把ui文件拷贝到生成的可执行文件中,不然会报错。举例:命令行中运行pyinstall httpclient.py --noconsole --hidden-import PySide2.QtXml 其中--noconsole指运行exe时不出现调试窗口(类似于你直接在pycharm中run后,显示错误的底部的4:run窗口)。如果要测试,可以去掉该命令,从而保留调试窗口,出现的错误会在该窗口中显示。--hidden-import PySide2.QtXml是因为这个QtXml库是动态导入,需要我们告诉PyInstaller,不然运行exe文件后,会在调试窗口中报错:No module named XXXX # 添加主窗口图标(打开EXE之后左上角显示的图标 # from PySide2.QtGui import QIcon # # app = QApplication([]) # # 加载icon # app.setWindowIcon(QIcon('logo.png')) # 应用程序图标,打包成的exe文件的图标 # 制作程序的时候,写上 # pyinstaller httpclient.py --noconsole --hidden-import PySide2.QtXml --icon="logo.ico" # 注意参数一定是.ico文件,不能使png等图片文件,可以通过在线的png转ico文件网站,生成ico # 比如https://www.zamzar.com/convert/png-to-ico/或者https://www.easyicon.net/covert/ 使用pyinstaller过程中遇到的问题及解决办法: https://blog.csdn.net/weixin_42052836/article/details/82315118(Pyinstaller 打包发布经验总结)https://blog.csdn.net/qq_38232378/article/details/95767597?utm_medium=distribute.pc_relevant.none-task-blog-title-2&spm=1001.2101.3001.4242(解决PyInstaller vcruntime140.dll没有被指定在Windows上运行)命令(生成单个程序,附带图标,生成在指定文件夹,使用upx压缩) pyinstaller -F C:\Users\levovozzb\Desktop\bag\sp.py --distpath=C:\Users\levovozzb\Desktop\bag -i C:\Users\levovozzb\Desktop\bag\cs.ico --upx-dir=D:\ThunderDownload\upx-3.96-win64\upx.exe pyinstaller -F 待打包py文件路径 ----distpath=程序指定生成的文件夹路径 -i 图标ico地址 --upx-dir=程序upx.exe的存放路径 upx的下载网址:https://github.com/upx/upx/releases/tag/v3.95

     

    若使用upx进行打包,upx is not avaliable报错 解压安装包得upx.exe文件,将exe拷贝到pyinstaller目录下,我的是E:\anaconda\Scripts虚拟纯python环境中,放的是C:\Users\qq154\.virtualenvs\htkjproject-9aXzGckk\Scripts查看第三方包版本号pip list或者pip show XXX安装特定版本的第三方包pip install XXX==5.15.0苦苦处理两周的问题终于解决了。通过把相关包位置加到系统环境变量中或者直接都打包进去是最有效果的。简单粗暴,之后再慢慢的缩减不需要的包即可。我的问题是通过在.spec文件中处理,再打包解决的。流程如下: a = Analysis(['Client.py'], pathex=['E:\\PyCharm 2019.1.2\\pycharmprojects\\htkjproject', 'E:\\anaconda\\Lib\\site-packages\\PyQt5\\Qt\\bin', 'E:\anaconda\Lib\site-packages', 'E:\anaconda\Lib\site-packages\PyQt5', 'E:\anaconda\Lib\site-packages\pyqtgraph'], binaries=[], datas=[], hiddenimports=['PyQt5.QtOpenGL'], hookspath=[], runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False) # 打包出来的有290M(落泪.jpg),慢慢删减之后再更

     

    另外,如果使用anaconda的集成python环境,一般打出来的包会很大。。。。解决方式https://www.zhihu.com/question/268397385?sort=created( 用pipenv创建纯python的虚拟环境),https://blog.csdn.net/frostime/article/details/90523062(此在虚拟环境中,也可以直接pip安装,用pipenv install安装第三方包要检查,太慢,因此推荐pip install)在解决包很大的过程中,尝试在虚拟纯python环境中打包遇到问题:

    “could not find or load the Qt platform plugin ”windows", this application failed to start because no qt platform plugin could be initialized

    发现当把环境变量中的QT_QPA_PLATFORM_PLUGIN_PATH设置为虚拟环境中pyqt5的plugins路径就可以运行,具体原因尚未清楚。而且显然这样对软件的可移植性有很大限制。换台主机就要配置变量是一件很Emm的事情。。。https://www.thetopsites.net/article/50550987.shtml oh,终于解决了。参考https://stackoverflow.com/questions/47468705/pyinstaller-could-not-find-or-load-the-qt-platform-plugin-windows中回答的办法,不用管环境变量了,copying the ~PyQt5\Qt\plugins\platforms folder from the program's directory, generated by using pyinstaller --onedir main.py, to the folder holding the .exe file.即可总而言之,我在项目中解决文件过大的过程为: # 在命令行中输入 # 先基于对应版本的python生成虚拟python环境 pipenv install --python 3.7 # 启动虚拟环境 pipenv shell # 安装pyinstaller pip install pyinstaller # 安装UI所需的包 pip install pyopengl pip install pyqtgraph ... # 安装完之后打包 pyinstaller -F client.py # -F选项为生成一个文件,会保留调试窗,若不想保留可以使用-Fw # 运行生成的.exe,根据调试窗的问题完善代码。若有其他问题可以在.spec文件中修改之后,使用如下语句打包: pyinstaller -F client.spec

     

    单选框

    同一组的按钮,Ctrl多选右键button group建立新的按钮组

    tab页中布局

    tab widget中的tab布局,直接右键是没有layout选项的,要在他右上角的对象的框 的上层tabwidget右键才有layout

    显示样式

    参考http://www.python3.vip/tut/py/gui/qt_09/QSS类似于CSS,根据object(ID)修改,语言加#,根据class选不加选中最上mainwindow,下面属性中有个stylesheet,点一下,选三个点,输入样式代码selector,与webcss的语法基本没区别。 字体font-family,大小font-size,颜色color qt修改style之后有个bug,得去掉style代码之后,重新输入正确的flat勾选之后,按钮的边框就不见了可以自己加动态属性,在property下面的框选加号,一般选string,然后选择性的控制部分控件的style修改某控件内部,比如tabwidget的内容的字体颜色,#tabWidget * {\n    color:red\n} 若只修改直接子节点的style,则#tabWidget > * {}空格是内部的所有调色,alpha通道是调透明度Pseudo-States伪状态 QPushButton:hover {color: red} hover悬浮,鼠标放到此位置时变化disable状态,:disabled鼠标悬浮并且处于勾选checked状态,:hover:checked QPushButton:hover{ font-family:微软雅黑; font-size:15px; color: #1d649c; }

     

    指定背景色,background-color,背景图片background-image

    边框

    间隔,对外的margin四个就上右下左,顺时针,从上开始,也可margin-top,元素内容到内边框的边界为padding

    优先级 越靠近的style优先级越高,可直接针对对象修改style样式官方文档https://doc.qt.io/qt-5/stylesheet-reference.html#list-of-properties多线程后台任务 遇到较耗时的任务时,最好创建新的子线程去执行,避免主线程阻塞把要实现的功能做成函数,然后t = Thread(target=run)    t.start()第三方画图控件plotwidget载入,https://www.bilibili.com/video/av78483752?p=3 用widget占个位,右键promote,提升类,类名即PlotWidget,头文件为其所在的库,即from 头文件 import PlotWidget注意没有core,图中仅举例导入文件(即load)时,pyside2要比pyqt5多个注册,即在loader.load(“main.ui”)前loader.registerCustomWidget(pg.PlotWidget)。即要把PlotWidget从哪儿来的要注册一下 轴刻度为字符串功能,获取鼠标所在处刻度值 http://www.python3.vip/tut/py/gui/pyqtgraph-2/内嵌web浏览器   将ui文件转换为python代码,pycharm中选中ui文件,最上栏tools-external tools选择pyUIC,即完成

    画3D散点图

    详细的示例分析见: https://blog.csdn.net/qq_38025771/article/details/109896143需要采用的 pyqtgraph.openglopengl中的GLViewWidgetpyqtgraph功能强大,使用pip安装好后,可以在命令行中运行下行代码,查看pyqtgra图例 python -m pyqtgraph.examples 推荐资料 https://zmister.com/archives/187.html(做了解用)https://blog.csdn.net/API1_7/article/details/98249965(画图需要使用定时器,多线程,建议单独线程画图)采用普通widget升级(promote)到GLViewWidget #设置最小化与最大化按钮 self.setWindowFlags(QtCore.Qt.Window)  UI布局文件: # NodeStatus.py from PyQt5 import QtCore, QtGui, QtWidgets class Ui_NodeStatus(object): def setupUi(self, NodeStatus): NodeStatus.setObjectName("NodeStatus") NodeStatus.resize(695, 401) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(NodeStatus.sizePolicy().hasHeightForWidth()) NodeStatus.setSizePolicy(sizePolicy) NodeStatus.setStyleSheet("*{ \n" " font-family:微软雅黑;\n" " font-size:15px;\n" " color: #1d649c;\n" "}\n" "") self.verticalLayout = QtWidgets.QVBoxLayout(NodeStatus) self.verticalLayout.setObjectName("verticalLayout") self.guiplot = GLViewWidget(NodeStatus) self.guiplot.setObjectName("guiplot") self.guiplot.opts['distance'] = 50 # 设置初始镜头高度 self.verticalLayout.addWidget(self.guiplot) self.retranslateUi(NodeStatus) QtCore.QMetaObject.connectSlotsByName(NodeStatus) def retranslateUi(self, NodeStatus): _translate = QtCore.QCoreApplication.translate NodeStatus.setWindowTitle(_translate("NodeStatus", "节点状态")) from pyqtgraph.opengl import GLViewWidget 主文件中画图进程类和子窗口(显示3d散点图)类 from PyQt5.QtWidgets import QApplication, QMainWindow, QDialog from PyQt5.QtCore import QThread, pyqtSignal, QTimer, Qt from PyQt5 import uic import pyqtgraph.opengl as gl import numpy as np class PlotNode(QThread): ''' 给予画图多线程 提供点数据分析功能 ''' signal = pyqtSignal(object, object, object, object) # 定义信号,根据信息交互设置参数位 def __init__(self): super(PlotNode, self).__init__() self.nodeStatus = {} def plotNodeStatus(self): ''' 初始化画散点图所需的点数据:position,size,color :return: ''' self.distance = 0 # 用于3D图的视角高度设定 self.num = self.nodeStatus['num_nodes'] self.x = self.nodeStatus['x'] self.y = self.nodeStatus['y'] self.z = self.nodeStatus['z'] self.running = self.nodeStatus['running'] self.malicious = self.nodeStatus['malicious'] # 设置numpy.ndarray类型的数据,用于画散点图 self.pos = np.empty((self.num, 3)) self.size = np.empty((self.num)) self.color = np.empty((self.num, 4)) for i in range(self.num): self.pos[i] = (self.x[i], self.y[i], self.z[i]) max_num = max(self.x[i], self.y[i], self.z[i]) if abs(self.distance) < abs(max_num): self.distance = abs(max_num) self.size[i] = 0.5 if self.running[i] == True and self.malicious[i] == False: self.color[i] = (0.0, 1.0, 0.0, 0.5) # 绿色 elif self.malicious[i] == True: self.color[i] = (1.0, 0.0, 0.0, 0.5) # 红色 else: self.color[i] = (1, 1, 1, 0.5) # 灰色 QThread.msleep(100) # 等待100毫秒 def run(self): for _ in range(300): self.plotNodeStatus() self.signal.emit(self.distance, self.pos, self.size, self.color) # 向连接槽发射信号 self.y class ChildWinNodeStatus(QDialog, Ui_NodeStatus): def __init__(self): super(ChildWinNodeStatus, self).__init__() self.setupUi(self) # 设置窗口最小化与最大化按钮 self.setWindowFlags(Qt.Window) def child_plotNode(self, distance, pos, size, color): self.guiplot.clear() g = gl.GLGridItem() size_axes = distance * 3 g.setSize(x=size_axes, y=size_axes, z=size_axes) self.guiplot.addItem(g) sp = gl.GLScatterPlotItem(pos=pos, size=size, color=color, pxMode=False) self.guiplot.addItem(sp)

     

    子窗口与父窗口信息交互

    建议写好子窗口的相关功能,在父窗口中对子窗口的内容进行相关操作。相关资料: https://blog.csdn.net/API1_7/article/details/98249965(PyQt/PySide2 中的 信号与槽 (pyqtSignal/Signal) 、多线程 (QThread) 和 定时器 (Timer))https://github.com/binghan523/pyqt_fatherAndChildSignalCommunication/tree/master/pyqt_fatherAndChildSignalCommunication/pyqt_fatherAndChildSignalCommunication(子窗口与父窗口之间互相发送接收消息)重点知识: 类与类之间信息触发采用pyqtsignal,且注意设置的参数个数与要传的参内容一致。signal.connect(func)绑定信号发送后触发的函数 from PyQt5.QtCore import pyqtSignal signal = pyqtSignal(object, object, object, object) # 定义信号,根据信息交互设置参数位,objetct是通用格式 # 主窗口中某函数,绑定 def ui_plotStatus_child(self, nodeStatus): '''接收到节点数据后,在子窗口上画图''' self.plot_thread.nodeStatus = nodeStatus self.plot_thread.signal.connect(self.ChildUI_NodeStatus.child_plotNode) self.plot_thread.start() # self.plot_thread是在主窗口中提前实例化的PlotNode类对象

     

    在父窗口中,要先初始化一个子窗口的实例,当要打开子窗口时,让实例.show()即可 self.ChildUI_NodeStatus = ChildWinNodeStatus() self.ChildUI_NodeStatus.show()

     

    在主窗口的函数中对子窗口实例进行操作。

    常见问题解决方法

    若某按钮被点击动作为打开新的子窗口,且该按钮cliced绑定函数A,若在A函数外调用窗口.show()方法,UI会卡死。 解决:当需要在A函数外打开该子窗口,则可写一句该button.clicked(),即相当于按钮被点击。不会卡死。

     

    pyqt5开发过程关键问题总结

    [点击查看](https://blog.csdn.net/qq_38025771/article/details/109675990)

     

     

     

     

     

     

     

     

     

     

     

     

         
    Processed: 0.016, SQL: 9