1

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.

zebov
  • 19
  • 1
  • 2

3 Answers3

2

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
Ferdinand Beyer
  • 64,979
  • 15
  • 154
  • 145
  • 1. Wouldn't it be easier to use a QTimer with interval 0 rather than the low-level QObject timer functions? 2. That will be a lot of events for little processing. Maybe do 10 to 100 iterations per event instead of just one? – Macke Aug 29 '11 at 06:43
  • @Macke: You could use *QTimer*, too, I guess. Don't see how this would make things easier, though. And yes, you could do multiple iterations before `yield`ing, but you would have to call `processEvents()` to update the GUI in between. By using just one iteration, you don't have to worry about that. Please note that this is just a "proof of concept", it's the OP's task to tweak it to his/her needs. – Ferdinand Beyer Aug 29 '11 at 06:48
  • AND what about threads? Would it be possible to do it on threads as PYQT should have its own threading. – zebov Aug 30 '11 at 18:41
  • @zebov: Of course you could use threads. BUT: There is not "real" threading in Python, because of the Global Interpreter Lock (look that term up for more information). Moreover, setting up threads and synchronized communication to check if the worker thread should stop is much more effort than this solution. – Ferdinand Beyer Aug 31 '11 at 07:43
  • As far as I know PYQT has its own threading independent from platform, in python 3 threading are different - much more better and without GIL. Just would like an example how to do this with threads. If someone would be so kind :) Thanks ! – zebov Aug 31 '11 at 16:25
  • Both Python and Qt threads are nothing else then operating system threads. Both have the same issues caused by the GIL. The same is true for Python 3.0, there's *nothing* different. You won't be able to execute multiple bits of Python code in parallel. Why do you insist of using threads? I won't give you a working example, but look at `QThread`, plus `QMutex` for synchronization. – Ferdinand Beyer Aug 31 '11 at 21:28
  • Agreed; you most likely don't need threads for this simple problem. Exceptions might be if the loop contains blocking I/O calls or long processing routines that release the GIL while running. – Luke Sep 09 '11 at 10:52
2

Ferdinand's answer is nice in that it avoids the use of processEvents() to make your own event loop. However, I think there's a much simpler solution: why not just set a flag when the stop button is pressed and exit the loop if the flag has been set? Something like:

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()
Luke
  • 11,374
  • 2
  • 48
  • 61
-1

I would like to give my solution to this problem.

I had a similar issue while creating a loop for taking real time photos from a sensor using PyQt.

I found that using QTimer was the only working solution for me, having tried the yield one and the check for self.stop is True.

Since the thread is very outdated, I'm going to use another example which is very similar to the one published here.

We want to init a counter with some kind of signal (in this case a keystroke) and then we want to stop it with another keystroke.

We are going to use the QTimer object, upgrading a counter during the timeout()signal which is emitted by the Timer.

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

This worked, at least for me! Hope this helped someone!

Giacomo
  • 31
  • 3
  • This does not address the threading question posted. QTimer may have been the only solution for you, but that doesn't make it a solution for the OP. This should be a comment. – Prune Oct 26 '15 at 18:36
  • Sorry, I thought it was an alternative solution to the problem, because it was the only working solution for me. – Giacomo Oct 26 '15 at 19:08