PYQT - ¿Cómo cancelar el bucle en mi GUI usando el botón cancelar?

He estado luchando con esto durante algún tiempo. Intentaré explicar lo que quiero hacer, tal vez ustedes puedan ayudarme.

Entonces, digamos que tengo una GUI con una etiqueta de estado y dos bucles que se ven así:

for _a in range(3000):
     self.changeLabel('_a= '+ str(_a))

for _b in range(5000):
     self.changeLabel('_b=' + str(_b))

def changeLabel(self,_text):
     self.ui.STATUS.setText(_text)   <---ui is a GUI where label is placed. 
     APP.processEvents()               

Quiero que se actualice una etiqueta (ESTADO) con resultados después de presionar (listo) START, y quiero cancelar los bucles cuando se presione el botón STOP.

Cómo lograr esto usando Threads, QEventloop o cualquier otra forma (si existe). Soy bastante principiante con PyQT, así que si alguien tiene alguna idea, compártala.

Gracias.

preguntado el 27 de agosto de 11 a las 17:08

3 Respuestas

La forma más sencilla de lograrlo es mediante el uso de generadores y un "temporizador inactivo".

La idea es convertir su bucle en un generador usando el yield palabra clave, de modo que pueda activar cada iteración desde fuera utilizando next(). Luego usa el temporizador de bajo nivel de Qt (startTimer(), killTimer()y timerEvent()) para crear un temporizador con intervalo cero, que se llama cada vez que no hay más eventos para procesar, para ejecutar la siguiente iteración del ciclo. Esto le da la oportunidad de reaccionar a los eventos de la GUI durante su ciclo, por ejemplo, para manejar el botón de parada clicked() señal.

class MyWidget(QWidget):  # Or whatever kind of widget you are creating

    def __init__(self, parent, **kwargs):
        super(MyWidget, self).__init__(parent, **kwargs)
        # ... Create your widgets, connect signals and slots, etc.
        self._generator = None
        self._timerId = None

    def loopGenerator(self):
        # Put the code of your loop here
        for a in range(3000):
            self.ui.STATUS.setText("a=" + a)
            # No processEvents() needed, just "pause" the loop using yield
            yield

    def start(self):  # Connect to Start-button clicked()
        self.stop()  # Stop any existing timer
        self._generator = self.loopGenerator()  # Start the loop
        self._timerId = self.startTimer(0)   # This is the idle timer

    def stop(self):  # Connect to Stop-button clicked()
        if self._timerId is not None:
            self.killTimer(self._timerId)
        self._generator = None
        self._timerId = None

    def timerEvent(self, event):
        # This is called every time the GUI is idle.
        if self._generator is None:
            return
        try:
            next(self._generator)  # Run the next iteration
        except StopIteration:
            self.stop()  # Iteration has finshed, kill the timer

Respondido 29 ago 11, 10:08

1. ¿No sería más fácil usar un QTimer con intervalo 0 en lugar de las funciones de temporizador QObject de bajo nivel? 2. Serán muchos eventos con poco procesamiento. ¿Quizás hacer de 10 a 100 iteraciones por evento en lugar de solo una? - peculiaridad

@Macke: Podrías usar QTimertambién, supongo. Sin embargo, no veo cómo esto facilitaría las cosas. Y sí, podrías hacer varias iteraciones antes yielding, pero tendrías que llamar processEvents() para actualizar la GUI en el medio. Al usar solo una iteración, no tiene que preocuparse por eso. Tenga en cuenta que esto es solo una "prueba de concepto", es tarea del OP ajustarlo a sus necesidades. - Ferdinand Beyer

¿Y los hilos? ¿Sería posible hacerlo en subprocesos ya que PYQT debería tener su propio subproceso? - zebov

@zebov: Por supuesto que podrías usar hilos. PERO: No hay subprocesos "reales" en Python, debido al bloqueo de intérprete global (busque ese término para obtener más información). Además, configurar subprocesos y comunicación sincronizada para verificar si el subproceso de trabajo debe detenerse es mucho más esfuerzo que esta solución. - Ferdinand Beyer

Hasta donde yo sé, PYQT tiene su propio subproceso independiente de la plataforma, en Python 3 los subprocesos son diferentes, mucho mejor y sin GIL. Solo me gustaría un ejemplo de cómo hacer esto con hilos. Si alguien fuera tan amable :) ¡Gracias! - zebov

La respuesta de Ferdinand es buena porque evita el uso de processEvents () para crear su propio bucle de eventos. Sin embargo, creo que hay una solución mucho más simple: ¿por qué no establecer una bandera cuando se presiona el botón de parada y salir del ciclo si la bandera se ha establecido? Algo como:

def stopClicked(self):
    self.stop = True

for _a in range(3000):
    self.changeLabel('_a= '+ str(_a))    
    if self.stop:
        self.stop = False
        break

def changeLabel(self,_text):
    self.ui.STATUS.setText(_text)   <---ui is a GUI where label is placed. 
    APP.processEvents()

Respondido el 09 de Septiembre de 11 a las 14:09

Me gustaría dar mi solución a este problema.

Tuve un problema similar al crear un bucle para tomar fotos en tiempo real desde un sensor usando PyQt.

Descubrí que usar QTimer era la única solución que funcionaba para mí, después de haber probado el rendimiento y el comprobar para self.stop es verdadero.

Dado que el hilo está muy desactualizado, voy a usar otro ejemplo que es muy similar al publicado aquí.

Queremos iniciar un contador con algún tipo de señal (en este caso, una pulsación de tecla) y luego queremos detenerlo con otra pulsación de tecla.

Vamos a utilizar el QTimer objeto, mejorando un contador durante el timeout()señal que emite el temporizador.

class MyExample(QObject):

    timer = QTimer()
    cont = 0

    def __init__(self):

        super(QObject, self).__init__()

        # !!! IMPORTANT PART !!!
        # Here we connect the timeout of the timer to the count
        # function!
        self.timer.timeout.connect(self.cont)

    def keyEvent(self, e):

        # Here we connect the keystroke to the event 
        # on the object!
        if e.key() == Qt.Key_B:

            self.start()

        elif e.key() == Qt.Key_S:

            self.stop()

    def start(self):
        # Number of milliseconds the timer waits until the timeout
        self.timer.start(1000)

    def stop(self):
        self.timer.stop()

    def count(self):
        # Increase the counter on timeout
        self.cont = self.cont + 1
        print self.cont

¡Esto funcionó, al menos para mí! ¡Espero que esto haya ayudado a alguien!

Respondido 26 Oct 15, 21:10

Esto no aborda la pregunta de subprocesos publicada. Es posible que QTimer haya sido la única solución para usted, pero eso no lo convierte en una solución para el OP. Esto debería ser un comentario. - Ciruela pasa

Lo siento, pensé que era una solución alternativa al problema, porque era la única solución que funcionaba para mí. - James

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.