基本的 PyQt 进度条

这是一个非常基本的进度条,只使用最低限度的需要。

将这整个例子读到最后是明智的。

import sys
import time

from PyQt5.QtWidgets import (QApplication, QDialog,
                             QProgressBar, QPushButton)

TIME_LIMIT = 100

class Actions(QDialog):
    """
    Simple dialog that consists of a Progress Bar and a Button.
    Clicking on the button results in the start of a timer and
    updates the progress bar.
    """
    def __init__(self):
        super().__init__()
        self.initUI()
        
    def initUI(self):
        self.setWindowTitle('Progress Bar')
        self.progress = QProgressBar(self)
        self.progress.setGeometry(0, 0, 300, 25)
        self.progress.setMaximum(100)
        self.button = QPushButton('Start', self)
        self.button.move(0, 30)
        self.show()

        self.button.clicked.connect(self.onButtonClick)

    def onButtonClick(self):
        count = 0
        while count < TIME_LIMIT:
            count += 1
            time.sleep(1)
            self.progress.setValue(count)

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

进度条首先导入如此 from PyQt5.QtWidgets import QProgressBar

然后它像 QtWidgets 中的任何其他小部件一样初始化

self.progress.setGeometry(0, 0, 300, 25) 方法定义了对话框中的 x,y 位置以及进度条的宽度和高度。

然后我们使用 .move() 向下移动按钮 30px 向下,这样两个小部件之间就会有一个 5px 的间隙。

这里 self.progress.setValue(count) 用于更新进度。使用 .setMaximum() 设置最大值也会自动为你计算值。例如,如果最大值设置为 50,那么由于 TIME_LIMIT 为 100,它将从 0 跳到 2 到 4%而不是每秒从 0 跳到 1 到 2。你还可以使用 .setMinimum() 设置最小值,强制进度条从给定值开始。

执行此程序将生成类似于此的 GUI。

StackOverflow 文档

正如你所看到的,在计数器满足 TIME_LIMIT 条件之前,GUI 肯定会冻结并且无响应。这是因为 time.sleep 导致操作系统认为程序陷入了无限循环。

QThread

那么我们如何克服这个问题呢?我们可以使用 PyQt5 提供的线程类。

import sys
import time

from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import (QApplication, QDialog,
                             QProgressBar, QPushButton)

TIME_LIMIT = 100

class External(QThread):
    """
    Runs a counter thread.
    """
    countChanged = pyqtSignal(int)

    def run(self):
        count = 0
        while count < TIME_LIMIT:
            count +=1
            time.sleep(1)
            self.countChanged.emit(count)

class Actions(QDialog):
    """
    Simple dialog that consists of a Progress Bar and a Button.
    Clicking on the button results in the start of a timer and
    updates the progress bar.
    """
    def __init__(self):
        super().__init__()
        self.initUI()
        
    def initUI(self):
        self.setWindowTitle('Progress Bar')
        self.progress = QProgressBar(self)
        self.progress.setGeometry(0, 0, 300, 25)
        self.progress.setMaximum(100)
        self.button = QPushButton('Start', self)
        self.button.move(0, 30)
        self.show()

        self.button.clicked.connect(self.onButtonClick)

    def onButtonClick(self):
        self.calc = External()
        self.calc.countChanged.connect(self.onCountChanged)
        self.calc.start()

    def onCountChanged(self, value):
        self.progress.setValue(value)

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

让我们分解这些修改。

from PyQt5.QtCore import QThread, pyqtSignal

此行导入 Qthread,这是一个 PyQt5 实现,用于在后台划分和运行程序的某些部分(例如:函数,类)(也称为多线程)。这些部分也称为线程。默认情况下,所有 PyQt5 程序都有一个主线程,而其他程序(工作线程)用于将额外的时间和过程密集型任务卸载到后台,同时仍然保持主程序的正常运行。

第二个导入 pyqtSignal 用于在工作线程和主线程之间发送数据(信号)。在这个例子中,我们将使用它来告诉主线程更新进度条。

现在我们已经将计数器的 while 循环移动到一个名为 External 的单独类中。

class External(QThread):
    """
    Runs a counter thread.
    """
    countChanged = pyqtSignal(int)

    def run(self):
        count = 0
        while count < TIME_LIMIT:
            count +=1
            time.sleep(1)
            self.countChanged.emit(count)

通过对 QThread 进行子分类,我们实际上将 External 转换为可以在单独的线程中运行的类。线程也可以随时启动或停止,从而增加它的好处。

这里 countChanged 是当前进度,pyqtSignal(int) 告诉工作线程发送的信号是 int 类型。虽然,self.countChanged.emit(count) 只是将信号发送到主线程中的任何连接(通常它也可以用于与其他工作线程通信)。

def onButtonClick(self):
        self.calc = External()
        self.calc.countChanged.connect(self.onCountChanged)
        self.calc.start()

def onCountChanged(self, value):
    self.progress.setValue(value)

单击按钮时,self.onButtonClick 将运行并启动线程。该线程以 .start() 启动。还应该注意,我们将之前创建的信号 self.calc.countChanged 连接到用于更新进度条值的方法。每次 External::run::count 更新时,int 值也会发送到 onCountChanged

这是 GUI 在进行这些更改后的外观。

StackOverflow 文档

它也应该感觉更敏感,不会冻结。