千家信息网

PyQt5 GUI 接收UDP数据并动态绘图的过程介绍

发表于:2026-10-31 作者:千家信息网编辑
千家信息网最后更新 2026年10月31日,这篇文章主要介绍"PyQt5 GUI 接收UDP数据并动态绘图的过程介绍",在日常操作中,相信很多人在PyQt5 GUI 接收UDP数据并动态绘图的过程介绍问题上存在疑惑,小编查阅了各式资料,整理出简
千家信息网最后更新 2026年10月31日PyQt5 GUI 接收UDP数据并动态绘图的过程介绍

这篇文章主要介绍"PyQt5 GUI 接收UDP数据并动态绘图的过程介绍",在日常操作中,相信很多人在PyQt5 GUI 接收UDP数据并动态绘图的过程介绍问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"PyQt5 GUI 接收UDP数据并动态绘图的过程介绍"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

目录
  • 1. Qt 的用法

  • 2. Pycharm 设置

    • 2.1 安装 Pyqt5 和 pyinstaller 包

    • 2.2 Pycharm pyqt工具配置

  • 3 UDP图形界面设计

    • 3.1 GUI设计

    • 3.2 将GUI文件转换为py文件

    • 3.3 widget窗体提升,整合matplotlib的功能

    • 3.4 GUI 设计结果

  • 4 多线程编程UDP通讯

    • 4.1 信号和槽函数

    • 4.2 多线程

  • 5 Pyinstaller 打包成exe

    1. Qt 的用法

      pyqt5是qt的python版本,其主要是以对象的形式存在的,在编程的过程中无法可视化,带来诸多的不便。为了简化pyqt5的界面设计环节,我们可以使用qt中的设计器Qt Designer (C:\Qt\5.12.11\mingw73_32\bin\designer.exe)来设计图形界面,生成的图形界面通常保存在*.ui后缀的文件中。

     pyqt5可以直接调用.ui文件,也可以通过pyqt5自带的pyuic.exe将设计好的.ui文件转换为.py格式的pyqt5类,供其他模块调用。

     Qt 的安装教程在CSDN论坛上有很多,在此不再赘述了,建议使用 Qt5, 高版本的Qt6目前还没有被 matplotlib 纳入后端支持。

    2. Pycharm 设置

     首先安利一波,Pycharm在代码颜色主题、功能界面、python环境切换、打开终端、jupyter notebook支持、变量查看、Markdown支持、console多开等方面具有较高的便利性,因此本人主要使用pychrm 进行相关代码的开发,推荐使用。

    2.1 安装 Pyqt5 和 pyinstaller 包

     在pycharm底部打开终端,并输入如下代码安装pyqt5包和pyinstaller包。Pyinstaller包是用来将pyqt5GUI设计打包成exe可执行文件的工具,有了这个工具,就可以将程序拷贝到其他windows电脑上使用。

    pip install pyqt5,pyinstaller

     后续还需要安装matplotlib包,按照类似的方式进行安装,不再赘述。在安装了pyqt5后,matplotlib会自动以pyqt5为后端,绘制出来的图像效果更好,所带的工具栏也更加实用,推荐日常使用。

    2.2 Pycharm pyqt工具配置

     在使用Qt进行界面设计时,可以在pycharm中将Qt软件自带的几个工具都配置为外部工具(这就是pycharm众多优点之一),方便随时调用。pycharm中以及点击文件-设置-工具-外部工具(英文版自行对照)即可进入外部工具添加界面。

    Qt Designer 工具(设计Qt 界面)

    程序路径:

    C:\Qt\5.12.11\mingw73_32\bin\designer.exe

    工作目录:

    $ProjectFileDir$

    Qt Creator 工具(设计Qt 界面)

     程序路径在对应环境的Script目录下:

    C:\Anaconda3\envs\tensor37\Scripts\pyuic5.exe

     参数设置如下:

    $FileName$ -o $FileNameWithoutExtension$.py

     工作目录:

    $ProjectFileDir$

    PyUI 工具(Qt UI界面转为python代码)

     程序路径:

    C:\Qt\Tools\QtCreator\bin\qtcreator.exe

     工作目录:

    $ProjectFileDir$

     完成上述设置后在右键菜单中可以打开designer.exe和creator.exe这两个GUI设计应用,选中.ui文件右键运行pyuic.exe则会生成同名的.py文件,文件中包含有能够产生相同GUI的pyqt5类。

    3 UDP图形界面设计

    3.1 GUI设计

     在pycharm的空白处右键选择外部工具,打开designer,新建Main Window窗体。

     根据需要,我设计了一个UDP网络编程的界面,主要功能是接收UDP客户端发来的正弦数据,保存数据到txt文件中并将其绘制在底部的widget (窗体部件)中。

     目标运行界面如下:

    3.2 将GUI文件转换为py文件

     设计好界面后,保存得到widget_recev.ui图形文件,在左侧的项目资源管理器中可以选中ui文件右键实用外部pyuic工具将其转换为widget_recev.py文件,供程序调用。这个操作在后续的调试中经常会用到,随时改动GUI随时生成新的py文件。新建的py文件会覆盖原来的内容,所以建议另建其他python模块调用该模块,避免信息丢失。

    3.3 widget窗体提升,整合matplotlib的功能

     这里需要注意的是,matplotlib中的FigureCanvas和GUI中的widget都是Qwidget的子类,matplotlib是无法直接在widget中绘图的,需要在Designer中将widget提升为Qwidget类。选中GUI中的widget右键选择提升窗口部件,选择Qwidget,给提升的类取一个好记的名字,在这里我使用的是mplwidget。

     生成的widget_recev.py最后面会生成一句:

    from mplwidget import mplwidget

     将其放在类文件的开头,否则会报错。

     mplwidget.py 模块需要自己构建,在对应的路径下自己建一个mplwidget.py文件,主要功能是创建一个同时继承了FigureCanvas与QWidget的类,按照上面预定义的,将其命名为mplwidget类。该操作使原来的widget窗体具有了matplotlib画布功能,可以在上面绘图了。mplwidget.py文件的内容如下:

    # _*_coding: UTF-8_*_# 开发作者 :TXH# 开发时间 :2021-09-05 14:42# 文件名称 :mplwidget.py# 开发工具 :Python 3.7 + Pycharm IDEfrom PyQt5 import QtGui,QtWidgetsfrom matplotlib.backends.backend_qt5agg \ import FigureCanvasQTAgg as FigureCanvasfrom matplotlib.figure import Figurefrom PyQt5.QtCore import QThreadclass MplCanvas(FigureCanvas,QThread):    def __init__(self):        self.fig = Figure()        FigureCanvas.__init__(self, self.fig)        FigureCanvas.setSizePolicy(self,        QtWidgets.QSizePolicy.Expanding,        QtWidgets.QSizePolicy.Expanding)        FigureCanvas.updateGeometry(self)class mplwidget(QtWidgets.QWidget):    def __init__(self, parent=None):        QtWidgets.QWidget.__init__(self, parent)        self.canvas = MplCanvas()        self.vbl = QtWidgets.QVBoxLayout()        self.vbl.addWidget(self.canvas)        self.setLayout(self.vbl)

    3.4 GUI 设计结果

     生成的pyqt5 UI(widget_recev.py)内容如下。该文件是根据Qt ui文件自动生成的,因此一般只需要知道里面有哪些部件即可,对于一些大小、位置设置的细节可以不用关注,因为已经在GUI设计的时候做好了。

    from PyQt5 import QtCore, QtGui, QtWidgetsfrom PyQt_Learning.UDP.GUI.mplwidget import mplwidget  # 根据mplwidget 的位置改动class Ui_Widget(object):    def setupUi(self, Widget):        Widget.setObjectName("Widget")        Widget.resize(280, 165)        self.label_2 = QtWidgets.QLabel(Widget)        self.label_2.setGeometry(QtCore.QRect(110, 10, 55, 16))        self.label_2.setObjectName("label_2")        self.lineEdit_2 = QtWidgets.QLineEdit(Widget)        self.lineEdit_2.setGeometry(QtCore.QRect(110, 30, 61, 21))        self.lineEdit_2.setObjectName("lineEdit_2")        self.pushButton = QtWidgets.QPushButton(Widget)        self.pushButton.setGeometry(QtCore.QRect(10, 120, 71, 24))        self.pushButton.setObjectName("pushButton")        self.label = QtWidgets.QLabel(Widget)        self.label.setGeometry(QtCore.QRect(12, 10, 55, 16))        self.label.setObjectName("label")        self.lineEdit = QtWidgets.QLineEdit(Widget)        self.lineEdit.setGeometry(QtCore.QRect(12, 30, 81, 21))        self.lineEdit.setObjectName("lineEdit")        self.pushButton_2 = QtWidgets.QPushButton(Widget)        self.pushButton_2.setGeometry(QtCore.QRect(180, 120, 75, 24))        self.pushButton_2.setObjectName("pushButton_2")        self.lineEdit_5 = QtWidgets.QLineEdit(Widget)        self.lineEdit_5.setGeometry(QtCore.QRect(190, 80, 61, 21))        self.lineEdit_5.setObjectName("lineEdit_5")        self.label_3 = QtWidgets.QLabel(Widget)        self.label_3.setGeometry(QtCore.QRect(190, 60, 71, 16))        self.label_3.setObjectName("label_3")        self.label_4 = QtWidgets.QLabel(Widget)        self.label_4.setGeometry(QtCore.QRect(10, 60, 71, 16))        self.label_4.setObjectName("label_4")        self.lineEdit_3 = QtWidgets.QLineEdit(Widget)        self.lineEdit_3.setGeometry(QtCore.QRect(10, 80, 51, 21))        self.lineEdit_3.setObjectName("lineEdit_3")        self.label_5 = QtWidgets.QLabel(Widget)        self.label_5.setGeometry(QtCore.QRect(110, 60, 71, 16))        self.label_5.setObjectName("label_5")        self.lineEdit_4 = QtWidgets.QLineEdit(Widget)        self.lineEdit_4.setGeometry(QtCore.QRect(110, 80, 51, 21))        self.lineEdit_4.setObjectName("lineEdit_4")        self.label_6 = QtWidgets.QLabel(Widget)        self.label_6.setGeometry(QtCore.QRect(70, 80, 21, 16))        self.label_6.setObjectName("label_6")        self.retranslateUi(Widget)        QtCore.QMetaObject.connectSlotsByName(Widget)    def retranslateUi(self, Widget):        _translate = QtCore.QCoreApplication.translate        Widget.setWindowTitle(_translate("Widget", "数据发送端"))        self.label_2.setText(_translate("Widget", "端口"))        self.lineEdit_2.setText(_translate("Widget", "9999"))        self.pushButton.setText(_translate("Widget", "发送正弦"))        self.label.setText(_translate("Widget", "IP地址"))        self.lineEdit.setText(_translate("Widget", "127.0.0.1"))        self.pushButton_2.setText(_translate("Widget", "停止发送"))        self.lineEdit_5.setText(_translate("Widget", "8"))        self.label_3.setText(_translate("Widget", "正弦通道数"))        self.label_4.setText(_translate("Widget", "正弦频率"))        self.lineEdit_3.setText(_translate("Widget", "50"))        self.label_5.setText(_translate("Widget", "正弦幅度"))        self.lineEdit_4.setText(_translate("Widget", "1"))        self.label_6.setText(_translate("Widget", "Hz"))

    4 多线程编程UDP通讯

     使用Qt进行界面设计非常方便。pyqt编程的难点在于底层的信号-槽函数机制以及多线程编程。先抛开多线程UDP编程,简单举例讲一下信号-槽函数的原理。

    4.1 信号和槽函数

     信号相当于是GUI主循环中的事件,一旦触发某个事件,对应的槽函数(对象方法)将会运行。

     信号可以是内建信号,也可以是自定义信号。内建信号一般直接跟部件相关联,可以根据一定的规则构建对应的槽函数,例如:

    def on_pushButtom_clicked(self):     ...def on_pushButtom_2_clicked(self):     ...def on_pushButtom_3_clicked(self):     ...

    分别对应着pushButtom,pushButtom_2,pushButtom_3 三个按钮被触发 clicked()事件时的自动关联槽函数,事件触发后立即运行对应的槽函数。类似的,checkBox_5被触发则默认自动关联如下槽函数,传递chencked 布尔信号:

    def on_checkBox_5_toggled(self,checked):    ...

     自定义信号则更加灵活,其在事件触发时通过emit()函数发送数据。在pyqt5中,信号发送的数据类型可以是python支持的任何类型, 目前的测试表明,numpy.array、list、str、int、float等数据类型可以通过信号传递给槽函数,作为槽函数的输入。

    在主线程(或GUI主循环)内,自定义简单的信号和槽函数对如下。

    from PyQt5.QtCore import QObjectfrom PyQt5 import QtCoreclass Test(QObject):    test_signal = QtCore.pyqtSignal(list)  # 定义test_signal 信号    def __init__(self, parent=None):        super().__init__(parent)        self.test_signal.connect(self.print_data)  # 将信号与test槽函数关联    def toggle(self):        a = list([1, 2, 3, 4, 5])        self.test_signal.emit(a)  # 向槽函数发送信号    @QtCore.pyqtSlot(list)    def print_data(self, list_var):  # 定义槽函数        # 槽函数一旦接收到test_signal 发送的数据,立即执行后续内容        print(list_var) test = Test()test.toggle()>>> [1, 2, 3, 4, 5]

     信号一般在初始化方法之前定义,作为Qt类的成员,定义信号时给出所发送信号的数据类型,以下例子中使用的是list类型。在初始化时将信号与对应的槽函数相关联。然后根据需要在不同的方法中发送信号给槽函数,槽函数接收到数据后立即执行函数中的内容。以上例子较为简单,主要在主线线程内进行信号和槽函数的触发。后文中将给出多线程进行信号和槽函数传递数据的案例。

    4.2 多线程

     pyqt的主界面使用的是主线程,可以看做是一个死循环。一旦主线程中产生了较为耗时的操作,将导致主线程出现假死的现象,体现在GUI界面就是无响应和无法进行任何操作。

     在进行GUI程序设计时一般遵循GUI界面和代码界面分开设计的原则,主线程只负责管理基本GUI的动作,而耗时的操作则通过子线程进行计算。

     回到"多线程UDP通讯"的主题,在创建好GUI的基础上,UDP通讯接收端的主函数如下。代码实现了主线程向子线程、子线程向子线程以及子线程向主线程传递数据的三种情况。

    当然,所有信号与槽函数的连接都必须在主线程中完成。
    具体方法是:在主线程中创建子线程实例,将子线程作为主线程的成员,如此可以实现子线程与子线程,子线程与主线程之间的信号传递。

    # _*_coding: UTF-8_*_# 开发作者 :TXH# 开发时间 :2021-08-26 18:24# 文件名称 :Receiver.py# 开发工具 :Python 3.7 + Pycharm IDEimport socketimport sysfrom PyQt5.QtWidgets import QApplication, QMainWindowfrom PyQt_Learning.UDP.GUI.widget_recev import Ui_MainWindowfrom PyQt5 import QtCore,uicfrom PyQt5.QtCore import QThread,pyqtSlotclass QmyDialog(QMainWindow): # 主窗体本身占用一个主线程    UDP_para = QtCore.pyqtSignal(list)    sender_para = QtCore.pyqtSignal(list)    def __init__(self, parent=None):        super().__init__(parent)        self.pause=False        self.statusBar().showMessage('Load UI...')        if 0:            self.ui = uic.loadUi('E:/Pywork/PyQt_Learning/UDP/GUI/widget_recev.ui',self) #        else:            self.ui = Ui_MainWindow()            self.ui.setupUi(self)        self.statusBar().showMessage('Init Canvas...')        self.canvas = self.ui.widget.canvas # 绘图设置        self.canvas.ax1 = self.canvas.fig.add_subplot(111)        self.canvas.ax1.get_yaxis().grid(True)        self.statusBar().showMessage('Init UDP...') # 状态栏更新        self.UDP = UDPThread(self.para(1)) # 创建子线程1        self.UDP_para.connect(self.UDP.UDP_para_update) # 主线程向UDP线程传递参数        self.statusBar().showMessage('Init plot sender...')        self.Plot_fig = Plot_Thread(self.para(2)) # 创建子线程2        self.UDP.send_data.connect(self.Plot_fig.send) # UDP子线程向绘图子线程发送数据        self.sender_para.connect(self.Plot_fig.Sender_para_update) # 主线程向绘图子线程传递参数        self.Plot_fig.plot_data.connect(self.plot_fig) # 绘图子线程将数据发给主线程plot_fig函数,让其绘图        self.statusBar().showMessage('Ready!')    def on_pushButton_clicked(self):  # 设置参数        self.update_udp_para()        self.update_sender_para()        self.statusBar().showMessage('Para changed...')    def on_pushButton_2_clicked(self):  # 接收数据        self.update_udp_para()        self.update_sender_para()        self.UDP.pause = False        self.Plot_fig.pause=False        self.UDP.start()        self.ui.lineEdit.setReadOnly(True)        self.ui.lineEdit_2.setReadOnly(True)        self.Plot_fig.start()        self.statusBar().showMessage('Receiving data...')    def on_pushButton_3_clicked(self):  # 停止接收和绘图        self.pause=True        self.update_udp_para()        self.update_sender_para()        self.statusBar().showMessage('Receiving paused!')    def plot_fig(self,temp): # 绘图函数,不计算,避免主线程阻塞,接收数据后立即绘图        self.canvas.ax1.clear()        self.canvas.ax1.plot(temp)        self.canvas.fig.tight_layout()        self.canvas.draw()    def update_udp_para(self): # UPD子线程参数设置        self.UDP_para.emit(self.para(1))    def update_sender_para(self):# 绘图计算子线程参数设置        self.sender_para.emit(self.para(2))    def para(self,flag):        if flag==1:            return list([self.ui.lineEdit.text(),int(self.ui.lineEdit_2.text()),self.pause])        else:            return list([int(self.ui.lineEdit_3.text()),self.pause])            # 定义UDP 接收线程类class UDPThread(QThread):    send_data = QtCore.pyqtSignal(str)    def __init__(self,udp_para_list):        super().__init__()        self.IP,self.Port,self.pause = udp_para_list        self.s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 设置socket协议为UDP        self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)    def run(self) -> None: # 死循环接收UDP数据        try:            self.s.bind((self.IP, self.Port))        except:pass        i = 1        with open('out.txt', 'w') as f: # 将获取的UDP数据保存到本地的txt中            while True:                temp = self.s.recv(1024).decode('utf-8')  # 接收 socket 数据                if i==1:                    self.send_data.emit(temp)                f.writelines(temp + '\n')                i=(i+1)%2000                if self.pause: # 判断是否跳出循环                    break    def UDP_para_update(self,udp_para_list):        self.Ip,self.Port,self.pause=udp_para_list# 定义绘图计算子线程类class Plot_Thread(QThread):    plot_data = QtCore.pyqtSignal(list)    def __init__(self,para_list):        super().__init__()        self.pause = False        self.data = []        self.max_len = para_list[0]        self.i=1    def change_Len(self,len): # 绘图长度设置        if len<1000:            self.max_len=1000        else:            self.max_len = len    # 接收UDP接收子线程发来的数据,并转发给GUI中的绘图方法    @pyqtSlot(str)    def send(self,data):         self.data.append(float(data))        if len(self.data)>self.max_len:            self.data=self.data[(len(self.data)-self.max_len):]        self.i=(self.i+1)%(1000)        if self.i==0:            self.plot_data.emit(self.data)  # 每收到 1000个点将data数据发给GUI进行绘图    def Sender_para_update(self,para_list): # 根据主线程的信号更新绘图长度        self.max_len,self.pause=para_list# if __name__ == "__main__":app = QApplication(sys.argv)  # 调用父类构造函数,创建窗体form = QmyDialog()  # 创建UI对象form.show()  #sys.exit(app.exec())  #

     效果显示如下:
    点击记录并绘图,程序在保存UDP接收的数据的同时,将部分数据发送到GUI界面进行绘图。

    5 Pyinstaller 打包成exe

     pyinstaller将代码打包成exe时会面临生成的exe过大的情况,一个很小功能的exe体积高达200M。归根到底是pyinstaller将一些相互关联的安装包都打包到exe中了,而大多数安装包在当前项目中并没有真正使用到。

     经测试,可以使用pipenv 创建一个干净的虚拟环境,降低exe的大小。 环境中只安装需要的pyinstaller, pyqt5, numpy等即可。在虚拟环境下生成的pyqt5 exe可执行文件只有几十兆。

     想进一步压缩可以下载 upx.exe,将放入pipenv虚拟环境下的Script文件夹中,pyinstaller打包的时候会自动调用。压缩量小,但算是有点效果的,毕竟没有其他补救措施了。

     在pipenv虚拟环境下运行如下代码:

    # Gen_EXE.pyimport oserror = os.system('pyinstaller --clean -Fw E:\\Pywork\PyQt_Learning\\UDP\\GUI\\Receiver.py  E:\\Pywork\\PyQt_Learning\\UDP\\GUI\\widget_recev.py E:\\Pywork\\PyQt_Learning\\UDP\\GUI\\mplwidget.py') # 添加所有相关的py文件if not error: print('成功生成exe文件!')

     最终的大小约为44M,个人感觉还行。

    到此,关于"PyQt5 GUI 接收UDP数据并动态绘图的过程介绍"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

    线程 文件 函数 信号 数据 绘图 设计 界面 工具 生成 代码 功能 环境 程序 窗体 开发 参数 关联 编程 过程 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 保险科技 互联网保险 互联网科技活动banner 伴你行互联网科技有限公司 彤创互联网科技有限公司 崇明区互联网教育科技诚信经营 互联网金融科技展 华夏恒生互联网科技013171 互联网科技大赛获奖感言 保险科技服务互联网营销体系 黑科技 体验场景 互联网大会 杭州披云加互联网科技有限公司 产业互联网科技服务公司 张召忠谈中国互联网科技 陕西云淼互联网科技公司 新氧科技和美国互联网有关系吗 宁波跨境商互联网科技有限公司 黑龙江互联网科技公司 承德科技产业互联网平台 互联网科技企业文化 武汉亿视互联网科技有限公司 亿账通互联网科技 济南华为互联网科技产业总部基地 宁波青枫互联网科技有限公司 2021互联网科技公司排名 中国互联网有什么科技 皮皮虾互联网科技有限公司 城市宝互联网科技有限公司 广州智享互联网科技 10位科技互联网大佬 河南商务圈互联网科技有限公司
    0