PyQt开发多功能上位机框架
一、应用场景
若涉及到多个功能的操作与观测,并且各功能之间互有联系的项目,均可参考此模板框架用PyQt开发上位机。有利于自动化测试的实现与工作效率的提升。
博主在做HIL仿真测试时,例如使用dSPACE台架涉及软件较多,有ModelDesk、ControlDesk、MotionDesk,三个软件一般分别应用于画场景、控制车辆、仿真演示三个功能,并且可能还需要用其他工具观测CAN或ETH数据,各个功能之间互有联系。dSPACE每个软件提供有Python API,便想用PyQt将三个软件的操作整合成一个上位机软件。
二、前期准备
前期需准备好QtDesigner、PyUic工具以及PyQt的库。QtDesigner用于绘制上位机界面并生成ui文件,PyUic用于将ui文件生成python文件。
三、设计框图
实现多个功能,并让功能之间互相联系,所以设计思路便是将功能模块化,每个功能对应一个python文件,每个python文件里将会设计一个对应的类。功能之间的联系使用main文件进行协调。
其中toolui文件里主要使用一个toolui类继承ui文件里的ui类,ui.py文件由ui.ui文件生成,具体原因接下来说明。
接下来以需要从excel中获取数据,然后进行计算,计算完之后在界面的输出框显示为模板例程,如下图,进行上位机设计思路讲解,每一部分功能会着重讲解常用的一些传参方式或方便拓展上位机功能的一些方法。另例程附件在最后。
四、界面显示——个性化定制界面&QT设计师界面分离设计
首先我们要解决界面显示的问题,将界面的设计单独作为一个模块toolui.py,然后在main.py文件里调用显示。
1、toolui模块
toolui.py代码
import ui
from PyQt5.QtWidgets import QMainWindow
class ToolUi(QMainWindow, ui.Ui_Form):
def __init__(self):
super(ToolUi, self).__init__()
self.setupUi(self)
ui.py代码,由ui.ui文件通过pyuic自动生成
toolui.py文件里定义了一个ToolUi类,并继承了ui.py文件里的Ui_Form类,然后初始化里调用setupUi函数即会初始化用qtdesigner画好的控件对象。
用toolui继承ui.py的方式,而不直接使用ui.py的原因是因为这样操作,可以将qtdesigner设计的界面,与你想要更改的定制化的界面的逻辑分开。
例如一些定制化的圆形控件,警告弹窗可以直接更新写在toolui.py里,关于qtdesigner的更改就直接更新在ui.py。否则,直接使用ui.py,qtdesigner生成更新ui.py文件后,定制化的更改会消失,需要再重写一遍定制化逻辑在ui.py里。
2、main模块
主模块初始代码如下。主程序入口,实例化MainWindow类为ui对象,ui调用run()方法,用实例化ToolUi类的tool_ui对象调用show()方法,即可显示界面。
import PyQt5
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import QObject, pyqtSignal
import sys
import toolui
class MainWindow(QObject):
def __init__(self):
super().__init__()
self.tool_ui = toolui.ToolUi() # Qt 界面实例
def run(self):
self.tool_ui.show() # 显示界面
# 主程序入口
if __name__ == "__main__":
app = PyQt5.QtWidgets.QApplication(sys.argv)
ui = MainWindow()
ui.run() # ui就会显示出来
sys.exit(app.exec_())
五、信号与槽机制——统一connect方法连接信号与槽
1、机制的作用
首先理解一下主程序入口的两行语句
app = PyQt5.QtWidgets.QApplication(sys.argv)
这句创建了一个 QApplication 对象,这是 PyQt5 中所有 GUI 应用程序的基础。QApplication 管理 GUI 应用程序的控制流和主要设置。
sys.exit(app.exec_())
这句为进入应用程序的主事件循环,并等待用户交互或事件发生。app.exec_() 会一直运行,直到应用程序关闭。
这两句代码通常位于 PyQt5 应用程序的主脚本的开始和结束部分。这样做是为了确保 GUI 应用程序能够正确地初始化和退出。
以上可以了解到GUI主应用程序占用了一个进程,除了GUI界面程序外,需要处理许多的事件,比如按下一个按钮弹窗,或者是有个界面需要定时读取时间。事件触发类便需要用到信号与槽,定时事件可以使用Qt定时器。如果是自己想要通过回调或者开另一个线程的方式来实现事件触发、定时事件,那么安全性较低,会遇到很多麻烦。
Qt的信号与槽提供了一种安全、类型安全的方式来处理对象间的通信。
2、添加connect方法
信号与槽机制允许一个对象(称为发送者)在其状态改变时发出一个信号,而另一个对象(称为接收者)可以监听这个信号并相应地执行一个操作,即槽函数。pyqt里信号与槽通过connect方法进行连接。
所以在MainWindow初始化里添加一个实例的connect方法来统一管理连接各个信号与槽,在MainWindow初始化的时候即调用。注意看下图self.connect()是自己定义的,真正的连接是使用机制里的connect()方法,封装在一起便于添加连接管理。
六、获取数据(import功能)——通过实例对象属性之间传输数据
在获取数据前,还需选择文件,选择文件涉及到对话框,先讲解如何定制弹窗。
1、个性化定制弹窗
想要实现按下pushButton即导入文件的按钮,弹窗显示路径文件对话框提供选择。可以在前面提到的toolui里实现。同理,其余的定制化都可以在toolui实现,并通过信号触发,或通过引用tool_ui对象的属性进行更改。
toolui里添加展示对话框show_dialog的方法,main文件里将pushButton按钮点击事件连接该方法。
2、获取数据
选择好了文件,有了文件路径,那该如何将路径传送给importer.py(避免和关键字import重名)去打开这个路径的文件获取数据?
信号传送给槽参数容易,但是让槽回传参数比较难。所以可以直接通过引用不同功能实例对象属性的方式直接取值。
如下图,先定义好importer模块需要使用的相关属性,文件路径以及获取出来的数据。
一样需要在main文件里实例化ImPorter,然后按下pushButton_2按钮,需要实现检查文件是否导入,如果没导入弹窗警告,如果导入则获取数据,所以可先将pushButton_2按钮连接到一个检查函数上。在检查函数上实现具体逻辑。
可看到不同实例之间传输数据的一个方式是直接通过属性赋值。具体警告弹窗实现如下:
具体的获取的数据如下:
七、数据计算(execute功能)——引用tool_ui实例进行数据显示
在上一步获取了数据之后,便可调用execute里的方法进行计算。在执行框里需要显示获取的数据,可以通过tool_ui的属性直接显示,但如果想将每个功能的显示分开的话,可以直接在execute里定义一个ui属性引用tool_ui对象进行显示。
Execute.py模块代码如下:
from PyQt5.QtCore import Qt
def show(data1, data2, edit1, edit2):
edit1.append(str(data1))
edit1.setAlignment(Qt.AlignCenter) # 设置文本居中
font = edit1.font()
font.setPointSize(50) # 初始字体大小,可能需要动态调整
edit1.setFont(font)
edit2.append(str(data2))
edit2.setAlignment(Qt.AlignCenter) # 设置文本居中
font = edit2.font()
font.setPointSize(50) # 初始字体大小,可能需要动态调整
edit2.setFont(font)
class ExeCute:
def __init__(self):
super().__init__()
self.sum = None
self.product = None
self.quotient = None
self.ui = None # 创建引用tool_ui对象的属性
def calculate(self, data1, data2):
self.sum = data1[0] + data2[0]
# 直接显示在界面上
show(data1[0], data2[0], self.ui.textEdit_3, self.ui.textEdit_4)
self.product = data1[1] * data2[1]
show(data1[1], data2[1], self.ui.textEdit_5, self.ui.textEdit_6)
self.quotient = data1[2] / data2[2]
show(data1[2], data2[2], self.ui.textEdit_7, self.ui.textEdit_8)
main文件里在获取数据后调用执行模块的计算函数。
执行效果如图:
八、输出结果(output功能)——自定义信号传输计算结果参数
可以使用定义对象属性的赋值方法进行传输,但这一部分主要介绍下如何使用自定义信号,传输参数,将结果发送给output模块,output模块进行显示。
首先需要继承QObject类,然后定义一个类属性transmit_result,实例化成一个PyqtSignal对象,可以传入三个int类型的参数。
注意:自定义信号需要是类属性,如果定义成实例属性将不能使用。这是因为信号和槽机制是基于 Qt 的元对象系统(Meta-Object System)的,它要求信号在类级别上定义。
连接out_put的结果展示函数,在执行计算之后,信号发送,并传入和、积、商三个结果。
Output模块代码如下:
输出效果图:
九、总结——附例程链接
最后,将本文实现PyQt多功能上位机用到的设计思想总结为以下几点:
- 将功能模块化,用主模块进行管理,方便功能随时拓展。
- tooui继承ui,方便定制化ui和qt设计ui分离设计。
- qt信号与槽机制,设计方法统一管理信号与槽连接,管理事件触发,方便拓展。
- 若单独一个功能模块需要频繁更新ui界面,可以通过引用tooui实例的方式,在当前模块下进行更新。
- 功能模块之间的数据传输,可使用不同功能实例对象属性互相赋值的方式,也可以使用自定义信号传参方式。
本文是在实践过程中总结的一些博主觉得很简洁通用的知识点,方便初学PyQt者上手。读者可通过给的例程进行其他多功能上位机项目的开发,当前给的例程非常简单,肯定有许多的不足,在设计思维上也有许多可以改进的地方,欢迎读者们持续交流学习,以下是例程链接。
链接: https://pan.baidu.com/s/1exhslb4kq2R1PDtwgY4sUg
提取码: jeyg
解压密码: yunxi