Python QT ProgressBar

При использовании следующего кода мое приложение останавливается через пару секунд. И в палатках я имею ввиду висит. Я получаю окно из Windows, говорящее wait или force close.

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

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)

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

Ответ 1

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

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

Один простой способ сделать это - запустить цикл с помощью таймера, а затем периодически вызывать qApp.processEvents, пока цикл запущен.

Вот демо script, которое делает это:

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

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

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


Как правило, при попытке запуска графического интерфейса одновременно с фоновым заданием первый вопрос, который задают, заключается в следующем: связана ли привязка IO или привязана к ЦП?

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

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

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

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

Для этого окончательного сценария единственным решением является использование отдельного процесса, а не отдельного потока, т.е. использование multiprocessing. Разумеется, это решение будет эффективно, только если целевая система имеет несколько ядер процессора. Если есть только одно центральное ядро ​​для процессора, в основном ничего не может быть сделано для помощи (кроме переключения на другую реализацию Python или на другой язык вообще).

Ответ 2

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

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