3

I'm very new to Python and I have made a very simple countdown timer. The GUI was created in Qt Designer. There is a spin box for input of seconds, a start button and an LCD number counter. The counter counts down fine using the code below:

     def start_btn_clicked(self):
         x = self.Minute_spinBox.value()
         for i in xrange(x,0,-1):
             time.sleep(1)
             print (i)

So that I could see what was happening as I played around with it, I added the print instruction so that it shows the countdown in the Python console as it runs. I then thought I could maybe quite easily have the LCD number display the countdown with something like:

    self.lcdNumber.display(i)("%SS")

But no matter what I try, I cant get it to show. With the line above, I get the first number displayed, but then I get an error saying:

    self.lcdNumber.display(i)("%SS")
    TypeError: 'NoneType' object is not callable

I have tried so many variations that I no longer know where I started and here was me thinking it would be simple. I'd love to know why I cant get it to display the countdown.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336

3 Answers3

3

Just adding one line of code to my original code will allow the lcdNumber to display the count...

def start_btn_clicked(self):
     x = self.Minute_spinBox.value()
     for i in xrange(x,0,-1):
         time.sleep(1)
         app.processEvents() # just this one line allows display of 'i' 
         self.lcdNumber.display(i)`

And works perfectly

1

The display function returns None, so doing None("%SS") obviously isn't allowed.

self.lcdNumber.display(i) is enough to show the countdown!


To let Qt paint the widgets while looping run the countdown from another thread. See an example.

import time
from threading import Thread
from PyQt4.QtGui import QApplication, QMainWindow, QLCDNumber

class Window(QMainWindow):

    def __init__(self):
        QMainWindow.__init__(self)
        self.lcdnumber = QLCDNumber(self)
        self.resize(400, 400)

        t = Thread(target=self._countdown)
        t.start()

    def _countdown(self):
         x = 10
         for i in xrange(x,0,-1):
             time.sleep(1)
             self.lcdnumber.display(i)

if __name__ == "__main__":
    app = QApplication([])
    window = Window()
    window.show()
    app.exec_()
cdonts
  • 9,304
  • 4
  • 46
  • 72
  • But 'code' self.lcdNumber.display(i)'code' doesnt show countdown, it only shows 1 at the end of the countdown nothing before that at all. –  Nov 21 '15 at 14:23
  • @Stevo Well, you need to put that line inside the loop, just like the `print` function. – cdonts Nov 21 '15 at 14:28
  • 'code' def start_btn_clicked(self): x = self.Minute_spinBox.value() for i in xrange(x,0,-1): time.sleep(1) self.lcdNumber.display(i) print (i) 'code' –  Nov 21 '15 at 14:43
  • sorry... I cant get the grip of this text box. –  Nov 21 '15 at 14:46
  • That's because the `sleep` function is blocking the main thread, so Qt isn't able to paint the widget. You'll need to create another thread and run the loop from in there. – cdonts Nov 21 '15 at 14:54
  • The latest example works as the stand alone only trying to trigger it from the start button and the spin box gives the same result... nothing untill "1" at the end of the count. –  Nov 21 '15 at 17:01
  • I think I have it working ... ill need to tweak things a little and try it –  Nov 21 '15 at 17:14
  • After some tinkering it works like a charm.. the start button is assigned to the lines that initiate the thread which in turn calls the countdown timer but the key thing is that its now created as a Daemon thread this allows me to interupt it as previously I had to crash the program to end it. –  Nov 22 '15 at 10:36
0

The for loop is blocking the GUI.

The slot connected to the button's clicked signal is processed synchronously. This means the event-loop must wait for the slot to return before it can process any more events (including the paint events needed for updating the GUI).

So you need to find a way to process these events whilst the for loop is running. There are various ways of doing this, such as using a QTimer or a QThread. But the simplest way of fixing your particular example would be to use QCoreApplication.processEvents.

Here's an example that shows how to do that:

import sys, time
from PyQt4 import QtCore, QtGui

class Window(QtGui.QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.spinbox = QtGui.QSpinBox(self)
        self.spinbox.setValue(5)
        self.lcdnumber = QtGui.QLCDNumber(self)
        self.button = QtGui.QPushButton('Start', self)
        self.button.clicked.connect(self.handleButton)
        layout = QtGui.QVBoxLayout(self)
        layout.addWidget(self.spinbox)
        layout.addWidget(self.lcdnumber)
        layout.addWidget(self.button)

    def handleButton(self):
        for tick in range(self.spinbox.value(), -1, -1):
            self.lcdnumber.display(tick)
            self.button.setEnabled(not tick)
            # continually process events for one second
            start = time.time()
            while time.time() - start < 1:
                QtGui.qApp.processEvents()
                time.sleep(0.02)

if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.setGeometry(500, 300, 300, 200)
    window.show()
    sys.exit(app.exec_())
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • cdonts method is definitely the least intrusive and simplest method, once its fixed with the Deamon, only four lines of code added to my original code. –  Nov 22 '15 at 10:42