from PyQt5 import QtCore
from PyQt5.QtCore import QTimer,QThread
from PyQt5.QtWidgets import QApplication, QMainWindow
import time
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import QMainWindow, QPushButton, QApplication
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import time
# coding=utf-8
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import time
'''
信号传参类型
pyqtSignal() #无参数信号
pyqtSignal(int) # 一个参数(整数)的信号
pyqtSignal([int],[str] # 一个参数(整数或者字符串)重载版本的信号
pyqtSignal(int,str) #二个参数(整数,字符串)的信号
pyqtSignal([int,int],[int,str]) #二个参数([整数,整数]或者[整数,字符串])重载版本
'''
class Mythread(QThread):
# 定义信号,定义参数为str类型
breakSignal = pyqtSignal(str, list)
def __init__(self, parent=None):
super().__init__(parent)
# 下面的初始化方法都可以,有的python版本不支持
# super(Mythread, self).__init__()
def run(self):
for i in range(2000000):
# 发出信号
print("i=",i)
a = [i, i + 1]
self.breakSignal.emit(str(i), a)
# 让程序休眠
time.sleep(1)
if __name__ == '__main__':
app = QApplication([])
dlg = QDialog()
dlg.resize(400, 300)
dlg.setWindowTitle("自定义按钮测试")
dlgLayout = QVBoxLayout()
dlgLayout.setContentsMargins(40, 40, 40, 40)
btn = QPushButton('测试按钮')
dlgLayout.addWidget(btn)
dlgLayout.addStretch(40)
dlg.setLayout(dlgLayout)
dlg.show()
def process(a, s):
# dlg.setWindowTitle(s)
btn.setText(a + str(s[0]))
# 创建线程
thread = Mythread()
# # 注册信号处理函数
thread.breakSignal.connect(process)
# # 启动线程
thread.start()
dlg.exec_()
app.exit()
前言
前几天刚学 PyQt 的图像界面,制作一个小窗口的时候,需要拉取网络验证码,当用户点击已有的验证码的时候,就开始获取下载新的验证码,然后刷新QLabel显示新的验证码。
做出来之后,发现如果网络不通畅,特别是用户密码输入出错时,下载新的验证码图片特别慢,这时的登陆窗口就卡住了,不一会就变成了“未响应”,等了好一会下载完了,程序才恢复响应。
网上找了一下问题的原因,说是UI主线程和工作线程没有分开,使用urllib等库的时候堵塞主线程,系统就将程序判断为未响应了。做法说是耗时的工作要分开线程,要继承QThread类,要重写run函数等等等等,可惜都没有一个具体的例子说明,也就探索了许久。这里给出我的做法,也作为一个自己的笔记。
准备
Python 2.7
PyQt4
sublime text 3
开始
刚开始用PyQt designer做出ui类,然后自己的窗口要么继承要么里面声明ui对象去使用里面的setupUi()函数。我用的是继承,然后调用类内部函数。
ui类代码如下:
# -*- coding: utf-8 -*-
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName(_fromUtf8("Dialog"))
Dialog.resize(400, 300)
self.pushButton = QtGui.QPushButton(Dialog)
self.pushButton.setGeometry(QtCore.QRect(150, 160, 112, 34))
self.pushButton.setObjectName(_fromUtf8("pushButton"))
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
Dialog.setWindowTitle(_translate("Dialog", "Dialog", None))
self.pushButton.setText(_translate("Dialog", "干大事!", None))
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
Dialog = QtGui.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())
执行效果如图:
用另一个文件新建一个类,继承上面的ui类:
# -*- coding: utf-8 -*-
from PyQt4 import QtCore, QtGui
#从 ui.py 文件里 import ui类
from example_ui import Ui_Dialog
import sys
import time
#新建自己的窗口类,继承 QDialog 和 ui类
class MyDialog(QtGui.QDialog,Ui_Dialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
#调用内部的 setupUi() ,本身对象作为参数
self.setupUi(self)
#连接 QPushButton 的点击信号到槽 BigWork()
self.pushButton.clicked.connect(self.BigWork)
def BigWork(self):
# 干一件大事... 耗时 10s
time.sleep(10)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
#新建类对象
Dialog = MyDialog()
#显示类对象
Dialog.show()
sys.exit(app.exec_())
运行效果如图:
额…和上面并没有什么不同,我们点击一下试试。
不出几秒,窗口就成了这样子。鼠标转几下之后,它又恢复了原样。
产生这个未响应的原因,是我的工作函数BigWork()和ui主线程是同个线程,它干着大事的时候,ui主线程就没有办法刷新自己,因为路被大事堵住了,要等大事做完之后才能刷新,系统就认为这个窗口这么长时间没有刷新肯定挂了,就变成了未响应。而且,这让用户体验也变得非常低,窗口在等待的时候,不仅仅不能点击,连移动窗口都不行,如果等的久了,还可能被用户kill掉,所以,分离工作线程是非常必要的。
PyQt也给我们提供了这么一个类:QThread
通过继承它然后重写里面的 run()函数,就可以很容易的新建一个线程,达到多线程的任务。
我们新建一个py文件,就起名叫做threads.py,代码如下:
# -*- coding: utf-8 -*-
from PyQt4 import QtCore, QtGui
import time
#继承 QThread 类
class BigWorkThread(QtCore.QThread):
"""docstring for BigWorkThread"""
def __init__(self, parent=None):
super(BigWorkThread, self).__init__(parent)
#重写 run() 函数,在里面干大事。
def run(self):
#大事
time.sleep(10)
相当于把BigWork()函数里的任务移到这个run()函数里来做。
要创建新进程也很简单,把原窗口类BigWork()函数改一下就可以了,代码如下:
def BigWork(self):
#import 自己的进程类
from threads import BigWorkThread
#新建对象
self.bwThread = BigWorkThread()
#开始执行run()函数里的内容
self.bwThread.start()
注意
为什么要将新进程对象声明为私有成员嘞?原因是,如果声明为局部变量,那么BigWork()函数执行完bwThread.start()这一句,也就是最后一句的时候,局部变量将会被销毁,子进程也就被kill了,这时候会报错:“QThread: Destroyed while thread is still running”。
网上有种说法,说可以调用wait()函数等它执行完,但我测试了一下,wait()函数的调用就不能退出主线程函数了…结果还是成了单线程。
高级用法
假如,我现在点一次按钮就干一次大事,但干着一次大事的时候,我不想同时开始干第二次大事,我就要把“干大事”这个按钮变成无效,等干完了第一次再恢复有效。
这时就可以用到信号和槽,子进程有一个信号,连接着主窗口的一个函数,这个函数复制处理“子进程干完活了之后要干什么”这个问题。
(感觉还是单线程呀!然而,这么做就不会出现“未响应”的情况了)
再比如,原来的BigWork()函数需要接受一个参数t,来决定这个大事要干多久,我们就可以把这个参数放到子线程类的构造函数中。
再再比如,我要子线程执行完之后有返回值,就可以把这个返回值放到子进程的信号里,随着信号一起发回。当然,接受这个信号的槽的形参也要做相应的变化。
下面给出一个完整的例子,但不包括ui类的定义。
子进程定义:
# -*- coding: utf-8 -*-
from PyQt4 import QtCore, QtGui
import time
#继承 QThread 类
class BigWorkThread(QtCore.QThread):
"""docstring for BigWorkThread"""
#声明一个信号,同时返回一个list,同理什么都能返回啦
finishSignal = QtCore.pyqtSignal(list)
#构造函数里增加形参
def __init__(self, t,parent=None):
super(BigWorkThread, self).__init__(parent)
#储存参数
self.t = t
#重写 run() 函数,在里面干大事。
def run(self):
#大事
time.sleep(self.t)
#大事干完了,发送一个信号告诉主线程窗口
self.finishSignal.emit(['hello,','world','!'])
信号声明不能在__init__()函数里,不然会报错:AttributeError: 'PyQt4.QtCore.pyqtSignal' object has no attribute 'emit'
主进程窗口的定义:
# -*- coding: utf-8 -*-
from PyQt4 import QtCore, QtGui
#从 ui.py 文件里 import ui类
from example_ui import Ui_Dialog
import sys
import time
class MyDialog(QtGui.QDialog,Ui_Dialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
#调用内部的 setupUi() ,本身对象作为参数
self.setupUi(self)
#连接 QPushButton 的点击信号到槽 BigWork()
self.pushButton.clicked.connect(self.BigWork)
def BigWork(self):
#把按钮禁用掉
self.pushButton.setDisabled(True)
#import 自己的进程类
from threads import BigWorkThread
#新建对象,传入参数
self.bwThread = BigWorkThread(int(1))
#连接子进程的信号和槽函数
self.bwThread.finishSignal.connect(self.BigWorkEnd)
#开始执行 run() 函数里的内容
self.bwThread.start()
#增加形参准备接受返回值 ls
def BigWorkEnd(self,ls):
print 'get!'
#使用传回的返回值
for word in ls:
print word,
#恢复按钮
self.pushButton.setDisabled(False)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
#新建类对象
Dialog = MyDialog()
#显示类对象
Dialog.show()
sys.exit(app.exec_())
pyqt5多线程更新ui
于 2022-01-12 21:36:25 首次发布