多线程
多线程技术设计三种方法,其中一种是使用计数器模块QTimer,一种是使用多线程模块QThread,还有一种使用事件处理的功能。
QTimer
如果要在应用程序汇总周期性地进行某项操作,比如周期性地检测主机的CPU,则需要用到QTimer(定时器)。QTimer类提供了重复和单次的定时器。要使用定时器,则需要先创建一个QTImer实例,将其timeout信号连接到相应的槽,并调用start。
# -*- coding: utf-8 -*-
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class WinForm(QWidget):
def __init__(self, parent = None):
super(WinForm, self).__init__(parent)
self.setWindowTitle("QTimer Demo")
self.listFile =QListWidget()
self.label = QLabel('显示当前时间')
self.startBtn = QPushButton("开始")
self.endBtn = QPushButton("结束")
layout = QGridLayout(self)
# 初始化一个定时器
self.timer = QTimer(self)
self.timer.timeout.connect(self.showTime)
layout.addWidget(self.label,0,0,1,2)
layout.addWidget(self.startBtn,1,0)
layout.addWidget(self.endBtn,1,1)
self.startBtn.clicked.connect(self.startTimer)
self.endBtn.clicked.connect(self.endTimer)
self.setLayout(layout)
def showTime(self):
time = QDateTime.currentDateTime()
timeDisplay = time.toString("yyyy-MM-dd hh:mm:ss dddd")
self.label.setText(timeDisplay)
def startTimer(self):
# 设置时间间隔并启动定时器
self.timer.start(1000)
self.startBtn.setEnabled(False)
self.endBtn.setEnabled(True)
def endTimer(self):
self.timer.stop()
self.startBtn.setEnabled(True)
self.endBtn.setEnabled(False)
if __name__ == '__main__':
app = QApplication(sys.argv)
form = WinForm()
form.show()
sys.exit(app.exec_())
演示弹出一个窗口,然后这个窗口10s后消失
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
if __name__ == '__main__':
app = QApplication(sys.argv)
label = QLabel("<font color=red size=128><b>Hello PyQT, 窗口10后消失</b></font>")
label.setWindowFlags(Qt.SplashScreen| Qt.FramelessWindowHint)
label.show()
QTimer.singleShot(10000, app.quit)
sys.exit(app.exec_())
QThread
QThread是Qt线程中最核心的底层类
使用线程时可以直接调用Thread实例,调用其start()函数即可启动线程,线程启动后,会自动调用其实现的run方法,该方法就先线程的执行函数
业务的线程任务就写在run函数中,当run退出之后线程就结束了。QThread有strarted和finished信号,可以为这两个信号指定槽函数,在线程启动和结束后指定一段代码进行资源的初始化和释放操作。
常用的方法
- start
- wait
- sleep
# -*- coding: utf-8 -*-
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import sys
class MainWidget(QWidget):
def __init__(self, parent =None):
super(MainWidget, self).__init__(parent)
self.setWindowTitle("QThread 例子")
self.thread = Worker()
self.listFile = QListWidget()
self.btnStart = QPushButton("开始")
layout = QGridLayout(self)
layout.addWidget(self.listFile,0,0,1,2)
layout.addWidget(self.btnStart,1,1)
self.btnStart.clicked.connect(self.slotStart)
self.thread.sinout.connect(self.slotAdd)
def slotStart(self):
self.btnStart.setEnabled(False)
self.thread.start()
def slotAdd(self,file_inf):
self.listFile.addItem(file_inf)
class Worker(QThread):
sinout= pyqtSignal(str)
def __init__(self, parent = None):
super(Worker,self).__init__(parent)
self.working = True
self.num = 0
def __del__(self):
self.working = False
self.wait()
def run(self):
while self.working == True:
file_str = "File index {0} ".format(self.num)
self.num += 1
# 发射信号
self.sinout.emit(file_str)
# 线程休眠2秒
self.sleep(2)
if __name__ == '__main__':
app = QApplication(sys.argv)
demo = MainWidget()
demo.show()
sys.exit(app.exec_())
虽然界面的数据显示和数据读写的分了,但是如果数据的读写非常的耗费时间,则会造成界面卡死
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
global sec
sec =0
def setTime():
global sec
sec += 1
lcdNumber.display(sec)
def work():
timer.start(1000)
for i in range(200000000):
pass
timer.stop()
if __name__ == "__main__":
app = QApplication(sys.argv)
top = QWidget()
top.resize(300,120)
layout = QVBoxLayout(top)
lcdNumber = QLCDNumber()
layout.addWidget(lcdNumber)
button = QPushButton("测试")
layout.addWidget(button)
timer = QTimer()
timer.timeout.connect(setTime)
button.clicked.connect(work)
top.show()
sys.exit(app.exec_())
我们用循环来模拟非常耗时的工作,当点击测试按钮之后,程序界面会直接停止响应,知道循环结束才重新更新,计时器始终显示为0。
在PyQt中所有的窗口都在UI的主线程中,这个线程执行耗时的操作就会阻塞UI线程,从而使窗口停止响应。如果窗口长时间没有响应就会影响用户体验。为了避免出现这种问题,要使用QThread开启一个新的线程,从而在这个线程完成耗时的操作。
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
global sec
sec = 0
class WorkThread(QThread):
trigger = pyqtSignal()
def __init__(self):
super(WorkThread, self).__init__()
def run(self):
for i in range(2000000000):
pass
self.trigger.emit()
def countTime():
global sec
sec += 1
lcdNumber.display(sec)
def work():
timer.start(1000)
workThread.start()
workThread.trigger.connect(timeStop)
def timeStop():
timer.stop()
print("运行结束用时",lcdNumber.value())
global sec
sec = 0
if __name__ == "__main__":
app = QApplication(sys.argv)
top = QWidget()
top.resize(300,120)
layout = QVBoxLayout(top)
lcdNumber = QLCDNumber()
layout.addWidget(lcdNumber)
button = QPushButton("测试")
layout.addWidget(button)
timer = QTimer()
workThread = WorkThread()
button.clicked.connect(work)
timer.timeout.connect(countTime)
top.show()
sys.exit(app.exec_())
WorkThread继承来自QThread类,重写其run函数,run()函数就是新的线程需要执行的,在run函数中就要执行一个循环,然后发射计算完成的信号。
事件处理
PyQt为事件处理体用了两种机制:高级的信号与槽机制,以及低级的事件处理程序。我们介绍低级的事件处理程序,即processEvents()函数的使用方法,他们的作用就是处理时间,简单的说就是刷新页面。
对于执行很耗时的程序来说,由于PyQt需要等待程序执行完毕只有才能进行下一步,在页面上表现为卡顿。
# -*- coding: utf-8 -*-
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
import time
class WinForm(QWidget):
def __init__(self):
super(WinForm, self).__init__()
self.setWindowTitle("实时刷新页面的例子")
self.listFile = QListWidget()
self.btnStart = QPushButton("开始")
layout = QGridLayout(self)
layout.addWidget(self.listFile,0,0,1,2)
layout.addWidget(self.btnStart,1,1)
self.btnStart.clicked.connect(self.slotAdd)
self.setLayout(layout)
def slotAdd(self):
for n in range(10):
str_n = "File index {0}".format(n)
self.listFile.addItem(str_n)
QApplication.processEvents()
time.sleep(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
form = WinForm()
form.show()
sys.exit(app.exec_())