PyQt ProgressBar

Al usar el siguiente código, mi aplicación se detiene después de un par de segundos. Y por puestos me refiero a que cuelga. Me sale una ventana de Windows que dice espera o fuerza de cierre.

Podría agregar que esto solo sucede cuando hago clic dentro de la ventana de la barra de progreso o cuando hago clic fuera de ella para que pierda el foco. Si comienzo el ejemplo y no toco nada, funciona como debería.

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) 

Usando esto de esta manera:

 app = QtGui.QApplication(sys.argv) bar = ProgressBar(total=101) bar.show() for i in range(2,100): bar.update_progressbar(i) time.sleep(1) 

Gracias por cualquier ayuda.

Debe permitir que los eventos se procesen mientras el bucle se está ejecutando para que la aplicación pueda seguir respondiendo.

Aún más importante, para las tareas de ejecución prolongada, debe proporcionar una forma para que el usuario detenga el ciclo una vez que se inicie.

Una forma sencilla de hacer esto es iniciar el ciclo con un temporizador y luego llamar periódicamente a qApp.processEvents mientras se ejecuta el ciclo.

Aquí hay un script de demostración que hace eso:

 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_()) 

ACTUALIZAR

Suponiendo que está utilizando la implementación en C de python (es decir, CPython), la solución a este problema depende completamente de la naturaleza de las tareas que deben ejecutarse simultáneamente con la GUI. Más fundamentalmente, está determinado por el CPython que tiene un locking de intérprete global (GIL) .

No voy a intentar ninguna explicación de GIL de CPython: en cambio, simplemente recomendaré ver este excelente video de PyCon de Dave Beazley, y lo dejaré así.


Generalmente, cuando se intenta ejecutar una GUI al mismo tiempo que una tarea en segundo plano, la primera pregunta que se debe hacer es: ¿Está la tarea vinculada a IO o CPU?

Si está vinculado a IO (p. Ej., Acceder al sistema de archivos local, descargar desde Internet, etc.), la solución suele ser bastante sencilla, ya que CPython siempre libera GIL para las operaciones de E / S. La tarea en segundo plano se puede realizar de forma asíncrona o mediante un subproceso de trabajo , y no es necesario hacer nada especial para mantener la interfaz gráfica de usuario receptiva.

Las principales dificultades ocurren con las tareas vinculadas a la CPU, cuando hay una segunda pregunta: ¿Se puede dividir la tarea en una serie de pequeños pasos?

Si puede, entonces la solución es enviar periódicamente solicitudes al hilo de la GUI para procesar su stack actual de eventos pendientes. El script de demostración anterior es un ejemplo crudo de esta técnica. Más generalmente, la tarea se llevaría a cabo en un subproceso de trabajo separado, que emitiría una señal de actualización de gui a medida que se completa cada paso de la tarea. (NB: es importante asegurarse de que el subproceso de trabajo nunca intente ninguna operación relacionada con la GUI).

Pero si la tarea no se puede dividir en pequeños pasos, ninguna de las soluciones de tipo de subprocesamiento habituales funcionará. La GUI se congelará hasta que la tarea se haya completado, ya sea que se usen o no los hilos.

Para este escenario final, la única solución es usar un proceso separado, en lugar de un hilo separado, es decir, hacer uso del módulo de multiprocesamiento . Por supuesto, esta solución solo será efectiva si el sistema de destino tiene múltiples núcleos de CPU disponibles. Si solo hay un núcleo de CPU para jugar, básicamente no se puede hacer nada para ayudar (aparte de cambiar a una implementación diferente de Python, o a un lenguaje diferente por completo).

cuando realice la progtwigción de la interfaz gráfica de usuario (GUI), necesita utilizar algún tipo de subprocesos múltiples, tendrá una secuencia de trabajo y otra que actualiza la interfaz gráfica de usuario y responde a los eventos del mouse y el teclado. Hay varias formas de hacerlo. Le recomiendo que eche un vistazo a este tutorial de QProgressBar que tiene un excelente ejemplo práctico de cómo usar QProgressBar.

En el tutorial están utilizando QBasicTimer, que es la forma de devolver el control al hilo principal para que pueda responder a los eventos de la GUI. Al usar time.sleep en tu código, estás bloqueando por un segundo el único hilo que se está ejecutando.