PyQt5
安装
安装PyQt5,PyQt5-tools
pip install PyQt5 -i https://pypi.douban.com/simple
pip install PyQt5-tools -i https://pypi.douban.com/simple
框架
import sys
from PyQt5.QtWidgets import QApplication,QWidget
if __name__ == '__main__':
# 1.创建程序
app = QApplication(sys.argv) # 打印sys.argv输出['D:/Workspace/yolov5-6.0/testqt.py']
# 2.创建界面对象
w = QWidget()
# 3.设置对象属性
w.setWindowTitle("第一个qt")
# 4.展示界面
w.show()
# 5.程序循环等待,监听有无事件发生
app.exec_()
如果删除掉w.show()
后运行程序,会有一个程序在运行但是没有任何界面。
Qt Designer
到下面这个文件夹中将designer.exe发送到桌面,以后.ui文件都可以用它打开
入门教程
多线程
推荐阅读:
九、PyQt5多线程编程
1. 介绍
背景:用户在点击按钮后,希望界面做出较快反应,最讨厌出现未响应状态。出现未响应说明当前线程在执行一个耗时操作,该操作完不成,界面也不能更新。
问题再现:现在使用sleep()模拟耗时操作,如果将其放在主线程中,我们点击按钮1,那么会出现:
此时按钮2点击不了,也不能关闭界面,整个界面无法操作。
原因分析:点击按钮1触发的槽函数是:
def click_1(self):
for i in range(10):
print("是UI线程中执行....%d" % (i + 1))
time.sleep(1)
运行python程序时,os为其分配一个线程,click_1
就运行在这个主线程中,函数没有运行完,其他都操作不了。
对比:先点击按钮2,再点击按钮1,会出现:
已经定义了自己的线程类:
class MyThread(QThread):
def __init__(self):
super().__init__()
def run(self):
for i in range(10):
print("是MyThread线程中执行....%d" % (i + 1))
time.sleep(1)
按钮2对应槽函数为:
def click_2(self):
self.my_thread = MyThread() # 创建线程
self.my_thread.start() # 开始线程
继承于专门的线程类,系统可以进行线程调度,不会出现一个线程一直占用cpu的情况,所以此时再点击按钮1后,两个任务交替执行。
解决方法:在实际开发中,负责UI展示,刷新的主线程要与耗时子线程分开,主线程负责刷新界面,展示界面。子线程负责耗时操作,如:网络交互、磁盘IO等。主子线程各司其职,保证系统正常运行,提升整体用户体验。
2. 主线程(直接操控ui)和子线程之间传参
参考:
6. PyQt5 中的多线程的使用(上)
7. PyQt5 中的多线程的使用(下)
数据的相互传递分为ui界面到任务线程和任务线程到ui界面。
-
第一种可以采用类属性的动态绑定来实现。参考:pyqt主线程给子线程传递参数的方法
-
第二种可以采用信号emit触发来实现。
下面是二者案例:
from form import Ui_Form
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QThread, pyqtSignal
import sys
import time
class MyWin(QWidget,Ui_Form):
"""docstring for Mywine"""
def __init__(self):
super(MyWin, self).__init__()
self.setupUi(self)
self.mythread = MyThread() # 实例化自己建立的任务线程类
self.mythread.signal.connect(self.callback) #设置任务线程发射信号触发的函数
def test(self): # 这里test就是槽函数, 当点击按钮时执行 test 函数中的内容, 注意有一个参数为 self
self.mythread.data = 5 # 这句就是给线程的实例化一个属性给其赋值,在线程里面就可以调用了
self.mythread.start() # 启动任务线程
def callback(self,i): # 这里的 i 就是任务线程传回的数据
self.pushButton.setText(i)
class MyThread(QThread): # 建立一个任务线程类
signal = pyqtSignal(str) #设置触发信号传递的参数数据类型,这里是字符串
def __init__(self):
super(MyThread, self).__init__()
def run(self): # 在启动线程后任务从这个函数里面开始执行
print(self.data) # 调用传递过来的数据
if __name__ == '__main__':
app = QApplication(sys.argv)
mywin = MyWin() # 实例化一个窗口小部件
mywin.setWindowTitle('Hello world!') # 设置窗口标题
mywin.show() #显示窗口
sys.exit(app.exec())
3.子线程与子线程之间传参
什么是主线程:
pyqt的主界面使用的是主线程,可以看做是一个死循环。一旦主线程中产生了较为耗时的操作,将导致主线程出现假死的现象,体现在GUI界面就是无响应和无法进行任何操作。
在进行GUI程序设计时一般遵循GUI界面和代码界面分开设计的原则,主线程只负责管理基本GUI的动作,而耗时的操作则通过子线程进行计算。
所有信号与槽函数的连接都必须在主线程中完成。
具体方法是:在主线程中创建子线程实例,将子线程作为主线程的成员,如此可以实现子线程与子线程,子线程与主线程之间的信号传递。
案例:
PyQt5 GUI 接收UDP数据并动态绘图(多线程间信号传递)
PyQt5学习笔记:子线程与子线程之间数据传输,利用主线程实现(包括主线程传给子线程参数实现)
界面和逻辑分离
分离,最主要的就是使用界面文件的方式:PyQt加载.ui文件的四种方法
总结一下,我们pyqt5其实主要讨论下面这两种方法
一、方法一(传统)
- 使用QtDesigner进行界面设计,保存为xxx.ui文件
- 在Pycharm中使用PyUIC生成xxx.py文件
- 编写代码调用生成的xxx.py
二、方法二(现代)
- 使用QtDesigner进行界面设计,保存为xxx.ui文件
- 在.py文件中直接调用.ui文件运行
三、对比
【PyQt】pyqt加载调用ui界面文件的两种方法
方法1:使用pyuic编译ui文件
优点:
- 允许继承,和普通类一样
- 运行程序时没有额外的负载
缺点:
- 每次修改ui文件时都得手动将 .ui 编译为 .py
方法2:在代码中使用loadUi直接加载ui文件
优点:
- 修改ui时py代码无需修改
- 编译额外时间
缺点:
- 不允许继承
- PyCharm中无法使用代码检查和自动补全
- 使用 uic.loadUi() 简单粗暴,不用编译 .ui文件,直接就可以加载调用,相当于是把 .ui 文件当做了一个资源文件。所以在发布软件的时候,原始的.ui文件就必须和.exe一起发布,就像img等资源文件一样,所以要考虑而能否直接将 .ui 文件 发布给用户(用户可能会破坏该文件),需要仔细考虑。
界面使用designer设计,逻辑通过读取.ui获取界面属性。这样频繁改动style不会影响逻辑代码。
早期的方式是:利用QtDesigner来设计界面,再通过批处理脚本pyuic5.bat将ui文件转换成python源文件。不过由于要响应事件操作,往往会将相应的槽函数写在ui的py文件里(前面的示例就是这样),这样,界面和逻辑的开发代码就混合在一起了,每一次的ui的更新都会伴随着转换后py文件的修改,很不合理。
PyCharm设置Qt-Designer、PyUIC、PyRcc外部工具
PyCharm安装PyQt5及其工具(Qt Designer、PyUIC、PyRcc)详细教程
理解了界面和逻辑分离,并进行了一系列对比是不是发现还是方法一(传统)比较好,如果使用该方法,就需要下面三板斧。
[PyCharm安装PyQt5及其工具(Qt Designer、PyUIC、PyRcc)详细教程]
加载图片的方法
计时器
...
self.timer = QTimer(self)
self.count = 0
self.timer.timeout.connect(self.showNum)
self.startCount()
def startCount(self):
self.timer.start(1000)
def showNum(self):
self.count = self.count + 1
print(self.count)
timer
走完1秒后会发出timeout
信号,触发槽函数showNum
打包
页面跳转
pyqt 主界面控制切换窗口方法
页面跳转controller法
python有关super.__init__()
Python中调用父类方法的三种方式
Python多继承supper调用父类MRO顺序
python中super().init()
继承父类和setupui
Python & PyQt学习随笔:PyQt主程序的基本框架
为什么要调用setupui
根据UI类派生一个子类
在主程序中,需要根据UI对应类以及QtWidgets派生一个新类,在该新类中实现所有槽函数的代码。
关于派生的新类请注意:
- 一定要有两个基类,一个是UI界面窗口的窗口类,一个是UI类本身;
- 一定要实现新类的构造方法,并在构造方法中调用父类的构造方法;
- 新类的构造方法中要调用
self.setupUi(self)
,setupUi
为PyUIC生成的UI类图形界面初始化的重要函数。
案例:
class w_ReadExecl(QtWidgets.QWidget,Ui_Ui_tableView):#派生一个新类
def __init__(self): #新类构造函数,必须有
super(w_ReadExecl, self).__init__() #调用父类构造函数,必须有
self.setupUi(self) #进行图形界面初始化,必须有
self.tableView.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked | QtWidgets.QAbstractItemView.SelectedClicked)
def showExcel(self): #按钮的槽函数
filename = self.e_InputFileName.text()
sheetname = self.inputSheetName.text()
hashead = self.inputHasHead.isChecked()
print(f"即将显示{filename}.[{sheetname }]"
........
装载ui.py的两种方式
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QApplication, QWidget
from ui_calc import Ui_Calc
# 方式一
class MyCalc(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_Calc()
self.ui.setupUi(self)
@pyqtSlot(int)
def on_inputSpinBox1_valueChanged(self, value):
self.ui.outputWidget.setText(str(value + self.ui.inputSpinBox2.value()))
@pyqtSlot(int)
def on_inputSpinBox2_valueChanged(self, value):
self.ui.outputWidget.setText(str(value + self.ui.inputSpinBox1.value()))
# 方式二
class MyCalc2(QWidget, Ui_Calc):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
@pyqtSlot(int)
def on_inputSpinBox1_valueChanged(self, value):
self.outputWidget.setText(str(value + self.inputSpinBox2.value()))
@pyqtSlot(int)
def on_inputSpinBox2_valueChanged(self, value):
self.outputWidget.setText(str(value + self.inputSpinBox1.value()))
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
win = MyCalc()
# win = MyCalc2()
win.show()
sys.exit(app.exec_())