PyQt ProgressBar

问题描述 投票:11回答:2

使用以下代码时,我的应用程序会在几秒钟后停止。摊位我的意思是挂起。我从Windows得到一个窗口,说等待或强制关闭。

我可能会补充说,只有当我在进度条窗口内单击或者单击它外部时它才会失去焦点。如果我开始这个例子并且不触摸任何东西它就会像它应该的那样工作。

from PyQt4 import QtCore
from PyQt4 import QtGui


class ProgressBar(QtGui.QWidget):
    def __init__(self, parent=None, total=20):
        super(ProgressBar, self).__init__(parent)
        self.name_line = QtGui.QLineEdit()

        self.progressbar = QtGui.QProgressBar()
        self.progressbar.setMinimum(1)
        self.progressbar.setMaximum(total)

        main_layout = QtGui.QGridLayout()
        main_layout.addWidget(self.progressbar, 0, 0)

        self.setLayout(main_layout)
        self.setWindowTitle("Progress")

    def update_progressbar(self, val):
        self.progressbar.setValue(val)   

像这样使用:

app = QtGui.QApplication(sys.argv)
bar = ProgressBar(total=101)
bar.show()

for i in range(2,100):
    bar.update_progressbar(i)
    time.sleep(1)

谢谢你的帮助。

python multithreading qt pyqt qprogressbar
2个回答
12
投票

您需要在循环运行时允许处理事件,以便应用程序可以保持响应。

更重要的是,对于长时间运行的任务,您需要为用户提供一种方法,以便在循环启动后停止循环。

一种简单的方法是使用计时器启动循环,然后在循环运行时定期调用qApp.processEvents

这是一个演示脚本,它可以做到这一点:

import sys, time
from PyQt4 import QtGui, QtCore

class ProgressBar(QtGui.QWidget):
    def __init__(self, parent=None, total=20):
        super(ProgressBar, self).__init__(parent)
        self.progressbar = QtGui.QProgressBar()
        self.progressbar.setMinimum(1)
        self.progressbar.setMaximum(total)
        self.button = QtGui.QPushButton('Start')
        self.button.clicked.connect(self.handleButton)
        main_layout = QtGui.QGridLayout()
        main_layout.addWidget(self.button, 0, 0)
        main_layout.addWidget(self.progressbar, 0, 1)
        self.setLayout(main_layout)
        self.setWindowTitle('Progress')
        self._active = False

    def handleButton(self):
        if not self._active:
            self._active = True
            self.button.setText('Stop')
            if self.progressbar.value() == self.progressbar.maximum():
                self.progressbar.reset()
            QtCore.QTimer.singleShot(0, self.startLoop)
        else:
            self._active = False

    def closeEvent(self, event):
        self._active = False

    def startLoop(self):
        while True:
            time.sleep(0.05)
            value = self.progressbar.value() + 1
            self.progressbar.setValue(value)
            QtGui.qApp.processEvents()
            if (not self._active or
                value >= self.progressbar.maximum()):
                break
        self.button.setText('Start')
        self._active = False

app = QtGui.QApplication(sys.argv)
bar = ProgressBar(total=101)
bar.show()
sys.exit(app.exec_())

UPDATE

假设你正在使用python的C实现(即CPython),这个问题的解决方案完全取决于必须与GUI同时运行的任务的性质。更基本的是,它由CPython确定有Global Interpreter Lock (GIL)

我不会尝试对CPython的GIL进行任何解释:相反,我只是建议观看Dave Beazley的这个优秀的PyCon video,并留下它。


通常,在尝试与后台任务同时运行GUI时,首先要问的问题是:任务是IO绑定还是CPU绑定?

如果它受IO限制(例如访问本地文件系统,从Internet下载等),那么解决方案通常非常简单,因为CPython总是为I / O操作释放GIL。后台任务可以简单地完成asynchronously,或由worker thread执行,并且不需要做任何特殊操作来保持GUI响应。

当遇到第二个问题时,主要困难发生在CPU绑定任务上:可以将任务分解为一系列小步骤吗?

如果可以,那么解决方案是周期性地向GUI线程发送请求以处理其当前的挂起事件堆栈。上面的演示脚本是这种技术的粗略示例。更常见的是,任务将在单独的工作线程中执行,当任务的每个步骤完成时,该线程将发出gui-update信号。 (注意:确保工作线程永远不会尝试任何与GUI相关的操作本身很重要)。

但是如果任务不能分解成小步骤,那么通常的线程类型解决方案都不会起作用。 GUI将冻结,直到任务完成,无论是否使用线程。

对于这个最终场景,唯一的解决方案是使用单独的进程,而不是单独的线程 - 即使用multiprocessing模块。当然,只有目标系统具有多个可用CPU核心时,此解决方案才有效。如果只有一个CPU核心可以使用,基本上没有什么可以帮助(除了切换到不同的Python实现,或完全不同的语言)。


0
投票

当你进行gui编程时你需要使用某种形式的多线程,你会有一个工作线程和一个更新gui并响应鼠标和键盘事件的线程。有很多方法可以做到这一点。我建议你看看这个QProgressBar tutorial,它有一个如何使用QProgressBar的优秀工作示例。

在本教程中,他们使用的是QBasicTimer,它可以将控制权交还给主线程,以便它可以响应GUI事件。通过在代码中使用time.sleep,您可以阻止一秒钟运行的唯一线程。

© www.soinside.com 2019 - 2024. All rights reserved.