Python Qt ProgressBar

при использовании следующего кода мое приложение останавливается через пару секунд. И под киосками я подразумеваю повешение. Я получаю окно из окон, говорящее ждать или заставить закрыть.

я мог бы добавить, что это происходит только тогда, когда я нажимаю либо внутри окна индикатора выполнения, либо когда я нажимаю снаружи, чтобы он потерял фокус. Если я начну пример и ничего не трогаю, он работает так, как должен.

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)

Спасибо за любую помощь.

2 ответов


вам нужно разрешить обработку событий во время выполнения цикла, чтобы приложение могло оставаться отзывчивым.

что еще более важно, для длительных задач вам нужно предоставить пользователю способ остановить цикл после его запуска.

один простой способ сделать это-запустить цикл с таймером, а затем периодически вызывать 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_())

обновление

предполагая, что вы используете реализацию C python (т. е. CPython), решение этой проблемы зависит полностью о характере задач, которые должны выполняться одновременно с GUI. Более фундаментально, это определяется CPython, имеющим глобальная блокировка интерпретатора (GIL).

Я не собираюсь пытаться объяснить GIL CPython: вместо этого я просто рекомендую наблюдая за этим отлично PyCon video Дэйв Бизли, и оставьте это.


Как правило, при попытке запустить GUI одновременно с фоновой задачей, первый вопрос, который нужно задать: связана ли задача с IO или с CPU?

если он связан с IO (например, доступ к локальной файловой системе, загрузка из интернета и т. д.), то решение обычно довольно простое, потому что CPython всегда выпускает GIL для операций ввода-вывода. Фон задание можно просто сделать асинхронно, иначе выполняется рабочий поток, и ничего особенного не нужно делать, чтобы держать GUI отзывчивым.

основные трудности возникают с задачами, связанными с ЦП, когда есть второй вопрос: Можно ли разбить задачу на ряд небольших шагов?

Если это возможно, то решение состоит в том, чтобы периодически отправлять запросы потоку GUI для обработки его текущего стека ожидающих событий. Демо-скрипт выше приведен грубый пример этой техники. Обычно задача выполняется в отдельном рабочем потоке, который выдает сигнал обновления gui по мере выполнения каждого шага задачи. (NB: важно убедиться, что рабочий поток никогда не пытается выполнять какие-либо операции, связанные с GUI).

но если задача не могу быть разбиты на небольшие шаги, то ни одно из обычных решений типа резьбы не будет работать. GUI просто замерзнет, пока задача не будет выполнена завершена ли потоки или нет.

в последнем случае, единственным решением является использование отдельной


когда вы программируете gui, вам нужно использовать некоторую форму многопоточности, у вас будет рабочий поток и тот, который обновляет gui и реагирует на события мыши и клавиатуры. Есть несколько способов сделать это. Я рекомендую вам взглянуть на это QProgressBar учебник это имеет отличный рабочий пример того, как использовать QProgressBar.

в учебнике они используют QBasicTimer, который является способом вернуть контроль в основной поток, чтобы он мог реагировать на события GUI. Используя time.sleep в вашем коде вы блокируете на одну секунду единственный поток, который работает.