PyQt ProgressBar

При использовании следующего кода мое приложение останавливается через пару секунд. И под киосками я подразумеваю зависания. Я получаю окно от 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)

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


person Tuim    schedule 07.11.2012    source источник


Ответы (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_())

ОБНОВЛЕНИЕ

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

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


Как правило, при попытке запуска графического интерфейса пользователя одновременно с фоновой задачей первый вопрос, который следует задать: связана ли задача с вводом-выводом или процессором?

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

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

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

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

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

person ekhumoro    schedule 07.11.2012
comment
Если я запускаю это, окно графического интерфейса также останавливается с ошибкой «Не отвечает, принудительно закрыть». Однако, если я подожду, пока все мои задачи будут завершены, нормальное приложение продолжится. - person Tuim; 08.11.2012
comment
@Туим. Мой сценарий — это просто демонстрация, основанная на коде в вашем вопросе. Это не универсальное решение, которое будет работать во всех ситуациях. Вам нужно обновить свой вопрос с правильным объяснением того, что вы пытаетесь сделать. О каких задачах вы говорите? Они привязаны к процессору или к вводу-выводу? Приложение, выполняющее задачи, вы написали сами и поэтому можете модифицировать? На каком языке она написана? И т. д. и т. д. - person ekhumoro; 08.11.2012
comment
Эти задачи представляют собой установку, например, распаковку zip-файлов, установку пакетов msi/deb и тому подобное. Но это не очень относится к делу. Приложение также написано на Python и полностью настраивается. Также я не ожидаю ответа, который можно скопировать! Я ожидаю подсказки в правильном направлении, а тот, что у вас есть, не кажется мне правильным направлением, я пробовал. Не в обиду. - person Tuim; 09.11.2012
comment
@Туим. Наоборот: характер задач, вероятно, единственное, что имеет отношение к вашему делу. Пожалуйста, смотрите обновление моего ответа. - person ekhumoro; 09.11.2012
comment
Спасибо за ваш обширный ответ. - person Tuim; 12.11.2012

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

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

person Marwan Alsabbagh    schedule 07.11.2012
comment
Я знаю об этом. Но они больше не используют многопоточность, как я в примере. - person Tuim; 07.11.2012
comment
@Tuim: да, не используйте time.sleep(). - person Junuxx; 07.11.2012
comment
@Tuim Я обновил ответ, чтобы объяснить, почему ваш код и учебные пособия не делают одно и то же. - person Marwan Alsabbagh; 07.11.2012
comment
Я думаю, что упоминание потоков здесь просто сбивает с толку. Здесь нет многопоточности, это просто возвращение управления циклу событий Qt, чтобы приложение могло оставаться отзывчивым. Перенос работы в отдельные потоки — одна из стратегий для достижения этой цели, но она никоим образом не обязательна. - person Dan Milburn; 07.11.2012
comment
Может быть, мне нужно добавить еще немного контекста. У меня есть консольное приложение, которое последовательно выполняет набор задач. Я просто хочу показать для этого индикатор выполнения, и, поскольку QT является предпочтительной библиотекой графического интерфейса, я обязан ее использовать. Приложение не имеет никакой модели потоков, кроме основного потока. Так что мне просто нужен фрейм графического интерфейса (в новом потоке или нет), чтобы отображать индикатор выполнения и обновлять этот индикатор всякий раз, когда задача завершается. - person Tuim; 08.11.2012