如何在没有多线程的情况下显示进度

时间:2014-12-10 07:23:12

标签: python multithreading pyqt progress

让我们说我有一个PyQt程序,它通过一个给定的目录,查找*JPEG个图像,并在每次找到它时进行一些处理。根据所选目录的大小,这可能需要几秒到几分钟。

我想让我的用户更新状态 - 最好使用" x文件处理y文件" 。如果没有,通过设置progressbar.setRange(0,0)的简单运行脉冲进度条也可以。

根据我的理解,为了防止我的GUI冻结,我需要一个处理图像的独立线程,以及每隔一段时间更新GUI的原始线程。

但我想知道我是否有可能在同一个帖子中同时做两件事?

3 个答案:

答案 0 :(得分:3)

是的,您可以使用processEvents轻松完成此操作,single-shot timer是为了这个目的而提供的。

我已经使用这种技术来实现一个简单的文件查找对话框。您需要做的就是启动使用{{3}}处理文件的函数,然后定期在循环中调用processEvents。这应该足以使用处理的文件数更新计数器,并允许用户在必要时取消该过程。

唯一真正的问题是决定拨打processEvents的频率。您调用它的次数越多,GUI的响应速度就越快 - 但这会以大大减慢文件处理速度为代价。因此,您可能需要进行一些实验才能找到可接受的折衷方案。

<强>更新

这是一个简单的演示,展示了如何构建代码:

import sys, time
from PyQt5 import QtWidgets, QtCore

class Window(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.button = QtWidgets.QPushButton('Start')
        self.progress = QtWidgets.QLabel('0')
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.button)
        layout.addWidget(self.progress)
        self.button.clicked.connect(self.test)
        self._stop = False
        self._stopped = True

    def test(self):
        if self._stopped:
            self._stop = False
            self.progress.setText('0')
            self.button.setText('Stop')
            QtCore.QTimer.singleShot(1, self.process)
        else:
            self._stop = True

    def process(self):
        self._stopped = False
        for index in range(1, 1000):
            time.sleep(0.01)
            self.progress.setText(str(index))
            if not index % 20:
                QtWidgets.qApp.processEvents(
                    QtCore.QEventLoop.AllEvents, 50)
            if self._stop:
                break
        self._stopped = True
        self.button.setText('Start')

if __name__ == "__main__":

    app = QtWidgets.QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())

答案 1 :(得分:0)

如果没有多线程,我无法实现你需要的东西,这是不可能的,因为gui只能在主线程中更新。以下是我使用多线程执行此操作的算法。

让我们说您的应用程序处理图像。然后有以下线程:

  1. 主线程(由GUI / QApplication派生的classes.exec()阻止)
  2. 计时器,例如,1秒间隔,更新变量并调用GUI线程中的插槽,用于更新用户界面中的变量。
  3. 正在处理电脑上图像的线程。

    def process(self):
        self._status = "processing image 1"
        ....
    
    def _update(self):
        self.status_label.setText(self._status)
    
    def start_processing(self, image_path):
        # create thread for process and run it
        # create thread for updating by using QtCore.QTimer()
        # connect qtimer triggered signal to and `self._update()` slot
        # connect image processing thread (use connect signal to any slot, in this example I'll stop timer after processing thread finishes)
        @pyqtSlot()
        def _stop_timer():
            self._qtimer.stop()
            self._qtimer = None
        _update_thread.finished.connect(_stop_timer)
    
  4. 在pyqt5中,可以从一个嵌套线程(第一级)分配pyqtvariable。因此,您可以使用setter和getter使变量成为pyqt变量,并在setter中更新gui,或者考虑如何自己完成。

答案 2 :(得分:0)

您可以使用python threading模块并在线程例程中发出信号。

这是一个有效的例子

from PyQt4 import QtGui, QtCore
import threading
import time

class MyWidget(QtGui.QWidget):

    valueChanged = QtCore.pyqtSignal(int)

    def __init__(self, parent=None):
        super(MyWidget, self).__init__(parent)
        self.computeButton = QtGui.QPushButton("Compute", self)
        self.progressBar = QtGui.QProgressBar()
        layout = QtGui.QVBoxLayout(self)
        layout.addWidget(self.computeButton)
        layout.addWidget(self.progressBar)
        self.computeButton.clicked.connect(self.compute)
        self.valueChanged.connect(self.progressBar.setValue)   

    def compute(self):
        nbFiles = 10
        self.progressBar.setRange(0, nbFiles)

        def inner(): 
            for i in range(1, nbFiles+1):
                time.sleep(0.5)  # Process Image
                self.valueChanged.emit(i)  # Notify progress 

        self.thread = threading.Thread(target = inner)
        self.thread.start()


if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    widget = MyWidget()
    widget.show()
    sys.exit(app.exec_())