pyqt多线程入门学习

pyqt多线程入门

在UI的设计中,主线程(QApplication.exec())必须保证事件循环不被阻塞来响应用户的输入,也就是不能把需要长时间运行的代码放在主线程,需要把这部分代码移到其他线程,通过信号与槽的机制来实现线程的通信。

1、计数器的多线程

基本功能:点击【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_())

程序卡死,界面显示未响应:

pyqt多线程入门学习_第1张图片

如果要使用子线程的思想,即单独开一个运行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_())

2、计数器的多线程

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自带的函数,被重写了。

3、进度条的多线程

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

其实这篇博客已经写的很好了,自己实现一次主要是出于学习目的。

你可能感兴趣的:(GUI从零开始,pyqt5,qt)