Python Pyqt5多线程更新UI代码实例(防止界面卡死)

下面是Python Pyqt5多线程更新UI代码实例(防止界面卡死)的完整攻略。

1. 背景

在使用PyQt5进行GUI开发时,假如某个计算机密集型的操作耗时较长,那么就可能会导致界面卡死,影响用户体验。为了避免这种情况,可以利用多线程更新UI的方式来解决。

2. 实现过程

2.1 创建线程

在PyQt5中利用QThread创建线程,在其run方法中执行需要进行的计算密集型操作,例如:

class Worker(QThread):

    finishSignal = pyqtSignal(int)

    def run(self):
        # 计算密集型操作
        # ...

    def __del__(self):
        self.wait()

2.2 进行信号槽连接

在主程序窗口中连接信号槽,将相关信号链接到主程序窗口的槽函数中。在槽函数中使用QMetaObject.invokeMethod() 方法来实现跨线程UI更新,例如:

class MainWindow(QMainWindow, Ui_MainWindow):

    def __init__(self):
        super().__init__()
        self.setupUi(self)

        self.worker = Worker(self)
        self.worker.finishSignal.connect(self.update_ui)

    def on_start_button_clicked(self):
        self.worker.start()

    @pyqtSlot(int)
    def update_ui(self, progress):
        QMetaObject.invokeMethod(self.progress_bar, "setValue", Qt.QueuedConnection, Q_ARG(int, progress))

以上代码中,在worker的finishSignal信号被触发时,将会执行主程序窗口的update_ui槽函数,该函数中通过QMetaObject.invokeMethod()来更新UI。

2.3 代码完整示例

import sys
import time
from PyQt5.QtCore import pyqtSignal, QThread, QMetaObject, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow
from ui_mainwindow import Ui_MainWindow


class Worker(QThread):

    finishSignal = pyqtSignal(int)

    def run(self):
        for i in range(101):
            time.sleep(0.1)
            self.finishSignal.emit(i)


class MainWindow(QMainWindow, Ui_MainWindow):

    def __init__(self):
        super().__init__()
        self.setupUi(self)

        self.worker = Worker(self)
        self.worker.finishSignal.connect(self.update_ui)

        self.start_button.clicked.connect(self.on_start_button_clicked)

    def on_start_button_clicked(self):
        self.worker.start()

    @pyqtSlot(int)
    def update_ui(self, progress):
        QMetaObject.invokeMethod(self.progress_bar, "setValue", Qt.QueuedConnection, Q_ARG(int, progress))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

以上代码中,我们通过Worker线程来模拟计算密集型操作,每隔0.1秒发射一次finishSignal信号,在主界面窗口的progress_bar进度条中进行更新。

3. 示例说明

3.1 示例1 - 多线程更新UI

我们在代码中添加一个QLabel控件,用于展示worker线程中的计数器变量。完整代码如下:

import sys
import time
from PyQt5.QtCore import pyqtSignal, QThread, QMetaObject, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow
from ui_mainwindow import Ui_MainWindow


class Worker(QThread):

    finishSignal = pyqtSignal(int)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.count = 0

    def run(self):
        for i in range(101):
            time.sleep(0.1)
            self.count = i
            self.finishSignal.emit(i)


class MainWindow(QMainWindow, Ui_MainWindow):

    def __init__(self):
        super().__init__()
        self.setupUi(self)

        self.worker = Worker(self)
        self.worker.finishSignal.connect(self.update_ui)

        self.start_button.clicked.connect(self.on_start_button_clicked)

    def on_start_button_clicked(self):
        self.worker.start()

    @pyqtSlot(int)
    def update_ui(self, progress):
        QMetaObject.invokeMethod(self.progress_bar, "setValue", Qt.QueuedConnection, Q_ARG(int, progress))
        QMetaObject.invokeMethod(self.label, "setText", Qt.QueuedConnection, Q_ARG(str, f"Count: {self.worker.count}"))


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

在点击start_button按钮事件后,我们会启动Worker线程执行计算密集型操作,此时在主窗口的之前创建好的progress_bar进度条上将会由10%到100%逐渐切换。而在主程序窗口的label标签中将会展示一个不断递增的,从0开始的计数器变量,该变量与worker线程的运行是在同步的。

3.2 示例2 - 多线程同时操作UI组件

在本示例中我们主要演示多个线程如何同时更新同一个UI组件。我们假设我们有两个人在分别进行下棋操作,他们会在不同的线程内进行操作和计算,而在UI显示这些下棋操作的时候,他们都需要更新同一个UI棋盘组件。我们将棋盘组件设计成一个QLabel,使用字母“A”来代表落子,而使用字母“B”来代表可以下子的空位。

import sys
import time
from PyQt5.QtCore import pyqtSignal, QThread, QMetaObject, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
from ui_mainwindow import Ui_MainWindow


class PlayWorker(QThread):

    finishSignal = pyqtSignal(list)  # 信号带参数

    def __init__(self, parent=None):
        super().__init__(parent)
        self.player = None  # 已放置棋子
        self.step = None  # 待放置位置

    def run(self):
        if self.step and self.player:
            self.finishSignal.emit([self.player, self.step])


class MainWindow(QMainWindow, Ui_MainWindow):

    def __init__(self):
        super().__init__()
        self.setupUi(self)

        self.last_player = ""  # 上一个下棋的人
        self.chessboard = [["B" for i in range(3)] for j in range(3)]  # 用列表记录棋盘状态
        self.chessboard_labels = [[QLabel() for i in range(3)] for j in range(3)]  # 用于显示棋盘的标签

        for row in range(3):
            for col in range(3):
                self.grid.addWidget(self.chessboard_labels[row][col], row, col)

        self.play_worker_player_1 = PlayWorker(self)
        self.play_worker_player_1.finishSignal.connect(self.update_chessboard)

        self.play_worker_player_2 = PlayWorker(self)
        self.play_worker_player_2.finishSignal.connect(self.update_chessboard)

        self.player_1_button.clicked.connect(self.on_player_1_button_clicked)
        self.player_2_button.clicked.connect(self.on_player_2_button_clicked)

    def on_player_1_button_clicked(self):
        self.last_player = "A"
        for row in range(3):
            for col in range(3):
                if self.chessboard[row][col] == "B":
                    self.chessboard[row][col] = self.last_player
                    self.play_worker_player_1.player = self.last_player
                    self.play_worker_player_1.step = (row, col)
                    self.play_worker_player_1.start()

    def on_player_2_button_clicked(self):
        self.last_player = "B"
        for row in range(3):
            for col in range(3):
                if self.chessboard[row][col] == "B":
                    self.chessboard[row][col] = self.last_player
                    self.play_worker_player_2.player = self.last_player
                    self.play_worker_player_2.step = (row, col)
                    self.play_worker_player_2.start()

    @pyqtSlot(list)
    def update_chessboard(self, move):
        player, step = move[0], move[1]
        row, col = step[0], step[1]
        self.chessboard_labels[row][col].setText(player)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

在上述代码中,我们创建了两个PlayWorker线程,它们分别代表不同的人。当任意一个人点击了自己的按钮,我们将遍历整个棋盘,找到下棋的空白位置。根据选手不同选举合适的worker线程来执行信号槽通信操作,在update_chessboard槽函数中进行棋盘UI组件的更新。

4. 总结

在本篇文章中我们讲解了如何利用PyQt5创建QThread线程并与主线程进行通信,实现多线程更新UI的效果。并展示了两个完整的程序案例来进一步演示该技术。虽然该技术可以解决界面卡死的问题,但使用多线程也会带来一定的额外开销,并且如果线程之间没有合理的同步机制,那么会存在操作顺序异常的风险,请开发人员谨慎使用。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Python Pyqt5多线程更新UI代码实例(防止界面卡死) - Python技术站

(0)
上一篇 2023年5月19日
下一篇 2023年5月19日

相关文章

  • Python五种下划线详解

    下面是关于Python五种下划线的详细讲解及示例说明。 一、概述 在Python中,下划线(_)有五种不同的使用方式,它们分别是: 单个下划线:命名约定,表示弱内部使用,不会呈现在from module import * 单个前置下划线:命名约定,表示为非导出属性或方法 单个后置下划线:用于避免与Python关键字名称的冲突 双前置下划线:名称修饰,用于使属…

    python 2023年5月18日
    00
  • Python中赋值运算符的含义与使用方法

    赋值运算符是Python中最基础的运算符之一,用于将一个值或变量赋值给一个变量名。赋值运算符的使用方法相对简单,但是掌握其含义及注意事项非常重要,本文将从以下几个方面详细讲解赋值运算符的使用。 赋值运算符的含义 Python中的赋值运算符包括=、+=、-=、*=、/=、//=、%=、**=,它们分别对应着不同的操作。其中最基础的是=赋值符号,用于将某个值或变…

    python 2023年6月5日
    00
  • Python代码调试技巧教程详解

    Python代码调试技巧教程详解 在Python编程中,我们经常需要进行代码调试,以解决程序中的错误和问题。本文将详细讲解Python代码调试技巧教程,包括调工具、调试方法和两个示例。 调试工具 在Python中,我们可以使用以下调试工具来进行代码调试: pdb:Python自带的调试工具,可以在代码中设置断点,以逐行执行代码并查看变量值。 PyCharm:…

    python 2023年5月13日
    00
  • Python爬取求职网requests库和BeautifulSoup库使用详解

    我来详细讲解一下。 标题 首先,我们需要确定本文主题和标题。通过阅读题目可以得知,我们要讲解 Python 爬取求职网的过程,需要用到 requests 库和 BeautifulSoup 库。因此,我们可以将文章主题和标题确定为: Python 爬取求职网 – requests 库和 BeautifulSoup 库使用详解 简介 接下来,我们需要对本文进行简…

    python 2023年5月14日
    00
  • python实现excel公式格式化的示例代码

    现在我来详细讲解一下“python实现excel公式格式化的示例代码”的完整实例教程。 什么是python实现excel公式格式化 在日常工作中,我们经常需要对excel表格进行各种操作,其中涉及到的公式也是必不可少的。因此,我们可以使用python编写程序来实现对excel公式的格式化。具体来讲,就是将表格中的公式以一定的格式输出,并且可以保留相应的公式运…

    python 2023年5月13日
    00
  • Python整数类型(int)详解

    Python中的整数类型 在数学中,整数就是正整数、零、负整数的集合。在Python中,对于整数的定义也与此相同。 强类型编程语言的整数,一般会限定整数长度,以分配不同的存储空间。因此整数类型的声明关键字会有:short、int、long、long long等,它们的长度依次递增,开发者需要根据实际数字的大小选用不同的类型。 而Python则不同,它只有一种…

    2022年11月20日
    00
  • Python判断字符串是否为空和null方法实例

    让我来给大家详细讲解一下“Python判断字符串是否为空和null方法实例”。 判断字符串是否为空 方法一:使用len函数 使用Python内置的len()函数来判断字符串是否为空,当字符串为空时,len()函数返回值为0,反之,则返回该字符串的长度。 示例代码: str1 = "" if len(str1) == 0: print(&q…

    python 2023年6月5日
    00
  • python多线程、网络编程、正则表达式详解

    以下是详细讲解“Python多线程、网络编程、正则表达式详解”的完整攻略,包括三个部分的讲解和两个示例说明。 Python多线程 Python多线程是指在一个程序内同时运行多个线程,从而实现并发执行的效果。Python提供了threading模块来实现多线程。下面是一个例子,演示如何使用threading模块创建和启动线程: import threading…

    python 2023年5月14日
    00
合作推广
合作推广
分享本页
返回顶部