PYQT - How to cancel loop in my GUI using cancel button?

zebov picture zebov · Aug 27, 2011 · Viewed 7.1k times · Source

I have been strugling with this for some time. I will try to explain what i want to do , maybe you guys could help me.

So lets say I have GUI with status label on it and Two loops that look like this:

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

I want a label (STATUS) to be updated with a results after START being pressed (done), and i want to cancel loops when STOP button is being pressed.

How to achieve this using Threads, QEventloop or any other way (if exists). I am pretty much beginner with PyQT so if someone have any idea - please share.

Thanks.

Answer

Ferdinand Beyer picture Ferdinand Beyer · Aug 29, 2011

The easiest way to achieve this is by using generators, and an "idle timer".

The idea is to turn your loop into a generator using the yield keyword, so that you can trigger each iteration from outside using next(). Then you use Qt's low-level timer (startTimer(), killTimer(), and timerEvent()) to create a timer with interval zero, that is called every time there are no more events to process, to run the next loop iteration. This gives you the opportunity to react to GUI events during your loop, e.g., to handle the stop button clicked() signal.

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