在UI的设计中,主线程(QApplication.exec())必须保证事件循环不被阻塞来响应用户的输入,也就是不能把需要长时间运行的代码放在主线程,需要把这部分代码移到其他线程,通过信号与槽的机制来实现线程的通信。
基本功能:点击【Start】按钮的时候,下面的文本标签每隔一秒自动加1。
import sys
import time
from PyQt5.QtWidgets import QDialog, QPushButton, QApplication, QLabel
class TestWindow(QDialog):
def __init__(self):
super().__init__()
btn1 = QPushButton(self)
btn1.move(100, 100)
btn1.setText("start")
btn1.clicked.connect(self.update) # 按钮连接到槽
self.sec_label = QLabel(self)
self.sec_label.move(150, 200)
self.sec_label.setText("0")
def update(self):
for i in range(20):
time.sleep(1) # 每隔一秒
self.sec += 1
self.sec_label.setText(str(self.sec))
if __name__ == '__main__':
app = QApplication(sys.argv)
timm = TestWindow()
timm.resize(500, 500)
timm.show()
sys.exit(app.exec_())
程序卡死,界面显示未响应:
如果要使用子线程的思想,即单独开一个运行time.sleep的线程。
首先引入一个库。
from PyQt5.QtCore import *
自定义一个子线程的类,需要使用信号与槽机制与外界交互。
class MyThread(QThread):
sec_changed_signal = pyqtSignal(int) # 信号类型:int
def __init__(self):
super().__init__(parent)
self.sec = 1000 # 默认1000秒
def run(self):
for i in range(self.sec):
self.sec_changed_signal.emit(i) # 发射信号
time.sleep(1)
在主线程里面创建线程,并接收信号,连接槽函数。
thread = MyThread()
thread.sec_changed_signal.connect(self.update)
btn1.clicked.connect(lambda :thread.start())
def update(self, sec, *args, **kwargs):
self.sec_label.setText(str(sec))
改进后的整体代码如下:
import sys
import time
from PyQt5.QtCore import *
from PyQt5.QtWidgets import QDialog, QPushButton, QApplication, QLabel
class TestWindow(QDialog):
def __init__(self):
super().__init__()
# self.sec = 0
btn1 = QPushButton(self)
btn1.move(100, 100)
btn1.setText("start")
# btn1.clicked.connect(self.update) # 按钮连接到槽
self.sec_label = QLabel(self)
self.sec_label.move(150, 200)
self.sec_label.setText("0")
thread = MyThread()
thread.sec_changed_signal.connect(self.update)
btn1.clicked.connect(lambda :thread.start())
def update(self, sec, *args, **kwargs):
self.sec_label.setText(str(sec))
class MyThread(QThread):
sec_changed_signal = pyqtSignal(int) # 信号类型:int
def __init__(self):
super().__init__()
self.sec = 1000 # 默认1000秒
def run(self):
for i in range(self.sec):
self.sec_changed_signal.emit(i) # 发射信号
time.sleep(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
timm = TestWindow()
timm.resize(500, 500)
timm.show()
sys.exit(app.exec_())
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class MyThread(QThread):
sinOut = pyqtSignal(str) # 自定义信号,执行run()函数时,从相关线程发射此信号
def __init__(self, parent=None):
super(MyThread, self).__init__(parent)
self.working = True
self.num = 0
def __del__(self):
self.working = False
self.wait()
def run(self):
while self.working:
file_str = 'File index {0}'.format(self.num) # str.format()
self.num += 1
# 发出信号
self.sinOut.emit(file_str)
# 线程休眠2秒
self.sleep(2)
class MainWidget(QWidget):
def __init__(self, parent=None):
super(MainWidget, self).__init__(parent)
self.setWindowTitle("QThread 例子")
# 布局管理
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)
# 创建新线程,将自定义信号sinOut连接到slotAdd()槽函数
self.thread = MyThread()
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)
if __name__ == "__main__":
app = QApplication(sys.argv)
demo = MainWidget()
demo.show()
sys.exit(app.exec_())
这个例子一开始有个不理解的地方:为什么在子进程的init函数里面调用run()函数就不会有结果,反而是去掉会正常执行,但代码里也没地方引用run()啊?通过换函数名字得出只有run()才能正常执行,应该是QThread自带的函数,被重写了。
from PyQt5 import QtWidgets, QtCore
import sys
from PyQt5.QtCore import *
import time
# 继承QThread
class MyThread(QThread):
# 通过类成员对象定义信号对象
jindu_signal = pyqtSignal(str)
def __init__(self):
super(MyThread, self).__init__()
def __del__(self):
self.wait()
def run(self):
for i in range(100):
time.sleep(0.05)
self.jindu_signal.emit(str(i))
self.jindu_signal.emit(str(100))
class Example(QtWidgets.QWidget):
def __init__(self):
super().__init__()
# 按钮初始化
self.button = QtWidgets.QPushButton('开始', self)
self.button.setToolTip('这是一个 QPushButton widget')
self.button.resize(self.button.sizeHint())
self.button.move(120, 80)
self.button.clicked.connect(self.start_login) # 绑定多线程触发事件
# 进度条设置
self.pbar = QtWidgets.QProgressBar(self)
self.pbar.setGeometry(50, 50, 210, 25)
self.pbar.setValue(0)
# 窗口初始化
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('OmegaXYZ.com')
self.show()
self.thread = None # 初始化线程
def start_login(self):
# 创建线程
self.button.setEnabled(False)
self.thread = MyThread()
# 连接信号
self.thread.jindu_signal.connect(self.call_backlog) # 进程连接回传到GUI的事件
# 开始线程
self.thread.start()
def call_backlog(self, msg):
self.pbar.setValue(int(msg)) # 将线程的参数传入进度条
if msg == '100':
# self.thread.terminate()
del self.thread
self.button.setEnabled(True)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
myshow = Example()
myshow.show()
sys.exit(app.exec_())
总结一下:子线程首先定义一个与主线程沟通的信号,向外传递一些信息,要传递的信息在run函数中书写。主线程中一般会把子线程的处理放在一个槽函数执行,在该函数中首先创建线程,然后处理子线程的signal信号(连接到另一个槽函数),最后开始该子线程。
感觉也没啥太大区别,给两个信号分别连接槽函数就行。
import time
from PyQt5.QtCore import QThread, pyqtSignal, QDateTime
from PyQt5.QtWidgets import QWidget, QLineEdit, QListWidget, QPushButton, QVBoxLayout, QLabel
class MyThread(QThread):
add_item = pyqtSignal(str)
show_time = pyqtSignal(str)
def __init__(self, *args, **kwargs):
super(MyThread, self).__init__(*args, **kwargs)
self.num = 0
def run(self, *args, **kwargs):
while True:
file_str = 'File index{0}'.format(self.num, *args, **kwargs)
self.num += 1
# 发送添加信号
self.add_item.emit(file_str)
date = QDateTime.currentDateTime()
currtime = date.toString('yyyy-MM-dd hh:mm:ss')
print(currtime)
self.show_time.emit(str(currtime))
time.sleep(1)
class Window(QWidget):
def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs)
self.setWindowTitle('多线程动态添加控件')
# x,y,w,h
self.setGeometry(800, 100, 500, 750)
# 创建QListWidget控件
self.listWidget = QListWidget()
# 创建按钮控件
self.btn = QPushButton('开始', self)
self.lb = QLabel('显示时间', self)
# 创建布局控件
self.vlayout = QVBoxLayout()
# 将按钮和列表控件添加到布局
self.vlayout.addWidget(self.btn)
self.vlayout.addWidget(self.lb)
self.vlayout.addWidget(self.listWidget)
# 设置窗体的布局
self.setLayout(self.vlayout)
# 绑定按钮槽函数
self.btn.clicked.connect(self.startThread)
# 声明线程实例
self.thread = MyThread()
# 绑定增加控件函数
self.thread.add_item.connect(lambda file_str:self.listWidget.addItem(file_str))
# 绑定显示时间函数
self.thread.show_time.connect(lambda time:self.lb.setText(time))
'''
@description:按钮开始,启动线程
'''
def startThread(self):
# 按钮不可用
self.btn.setEnabled(False)
# 启动线程
self.thread.start()
if __name__ == '__main__':
import sys
from PyQt5.QtWidgets import QApplication
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
基本参考https://blog.csdn.net/m0_37329910/article/details/91968549
其实这篇博客已经写的很好了,自己实现一次主要是出于学习目的。