Can't seem to get pyqt countdown timer to work - python

I've searched internet for days but can figure out how to put this code to work. It's a very simple gui (made on Qt Designer) with a lcd and a button. I want it to on the press of the button to start the countdown from 180 seconds back. In the first moment i was able to make to button decrease in one the value but after trying so many different things nothing is working. Can someone help me please? Probably is something very simple. Thank you.
# -*- coding: utf-8 -*-
import sys
import time
from PyQt4 import QtCore, QtGui
from relogio import Ui_relogiocc
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_relogiocc()
self.ui.setupUi(self)
self.timer = QtCore.QTimer()
text = "%d:%02d" % (180/60,180 % 60)
self.ui.QLCDNumber.display(text)
self.timer.start(1000)
self.ui.iniciar.clicked.connect(self.updateTimerDisplay)
def updateTimerDisplay(self):
self.inicio = 180
while self.inicio != 0:
text = "%d:%02d" % (self.inicio/60,self.inicio % 60)
self.ui.QLCDNumber.display(text)
self.inicio - 1
else:
self.timer.stop()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = StartQT4()
myapp.show()
sys.exit(app.exec_())

It seems there are a number of things you are missing here.
Firstly, the timer emits a timeout() signal whenever each timeout period completes. In your case, this is every one second. However, you're not connecting this signal to anything.
Secondly, your updateTimerDisplay contains the following line:
self.inicio - 1
This reads the value of self.inicio, subtracts 1 from it and then throws the result away. Because self.inicio's value doesn't change your updateTimerDisplay method goes into an infinite loop.
I'm guessing you meant
self.inicio -= 1
instead, which assigns the new value of self.inicio back to itself.
Ultimately, however, it seems you're trying to use your updateTimerDisplay method to start the timer, count it down and also update the display of the timer. I'd recommend breaking this method up to smaller methods.
Firstly, updateTimerDisplay should only update the display of the timer:
def updateTimerDisplay(self):
text = "%d:%02d" % (self.inicio/60,self.inicio % 60)
self.ui.QLCDNumber.display(text)
Secondly, you'll want a method to start the timer. Something like the following should do:
def startTimer(self):
self.inicio = 180
self.updateTimerDisplay()
self.timer.start(1000)
Of course, you'll also need to connect your iniciar button's clicked() signal to this function, instead of to updateTimerDisplay.
Finally, you'll need a method that handles a tick from the timer. Something like the following should do:
def timerTick(self):
self.inicio -= 1
self.updateTimerDisplay()
if self.inicio <= 0:
self.timer.stop()
You'll also need to connect the timeout() signal of the timer to this function, using something like:
self.timer.timeout.connect(self.timerTick)

As it has been told in the other answers your code contains some obvious mistakes. Here you have a complete working example (the UI is not created via Designer) that resets properly the counter every time the button is clicked (i.e. stops the timer before starting it again. If you don't do that and click the Start button before the timer has stopped then the counter counts faster for every button click).
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class MyMainWindow(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.central = QWidget(self)
self.hbox = QHBoxLayout(self.central)
self.lcd = QLCDNumber(self.central)
self.timer = QTimer(self)
self.start_time = 20
self.lcd.display("%d:%02d" % (self.start_time/60,self.start_time % 60))
self.start_button = QPushButton("Start", self.central)
self.hbox.addWidget(self.lcd)
self.hbox.addWidget(self.start_button)
self.setCentralWidget(self.central)
self.start_button.clicked.connect(self.restartTimer)
self.timer.timeout.connect(self.updateLCD)
def restartTimer(self):
# Reset the timer and the lcd
self.timer.stop()
self.start_time = 20
self.lcd.display("%d:%02d" % (self.start_time/60,self.start_time % 60))
# Restart the timer
self.timer.start(1000)
def updateLCD(self):
# Update the lcd
self.start_time -= 1
if self.start_time >= 0:
self.lcd.display("%d:%02d" % (self.start_time/60,self.start_time % 60))
else:
self.timer.stop()
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
ui = MyMainWindow()
ui.show()
sys.exit(app.exec_())

There are multiple things wrong with your code. For starters, you wrote self.inicio - 1 instead of -= 1, and you never actually use the Qtimer you create. But ignoring that, the structure of your program isn't right: Currently you call updateTimerDisplay when the user clicks your iniciar button and loop in there until your countdown reaches zero. What you want to do is start the timer when the user clicks the button and connect the timer (actually its timeout signal) to a method that just counts down one second and updates the display:
def startTimerDisplay(self):
""" set the countdown value and start the timer """
self.inicio = 180
self.timer.start(1000)
def updateTimerDisplay(self):
""" count down one second, set the text, and check if the timer should stop """
self.inicio -= 1
text = "%d:%02d" % (self.inicio/60,self.inicio % 60)
self.ui.QLCDNumber.display(text)
if self.inicio == 0:
self.timer.stop()
Alter your __init__ method to connect these functions like this:
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_relogiocc()
self.ui.setupUi(self)
self.timer = QtCore.QTimer()
text = "%d:%02d" % (180/60,180 % 60)
self.ui.QLCDNumber.display(text)
#connect the button to the start method ...
self.ui.iniciar.clicked.connect(self.startTimerDisplay)
#... and the timer to the update method
self.timer.timeout.connect(self.updateTimerDisplay)

Related

PyQt5 chang Label text using Loop

code it's clean from bugs but when is running program will freezing
the program its countdown using button to start the countdown
the picture below discripe the layout
enter image description here
the probelm is im using loop to changing label text and that make program freezed
from time import sleep
import PyQt5.QtWidgets as Qtw
class MainWindow(Qtw.QWidget):
def __init__(self):
super().__init__()
self.setLayout(Qtw.QVBoxLayout())
pushButton = Qtw.QPushButton("start",clicked = lambda: setting_label())
self.layout().addWidget(pushButton)
my_label = Qtw.QLabel("00:00:00")
self.layout().addWidget(my_label)
self.show()
def setting_label():
t = 1200
while t:
h = t // 3600
m = t // 60
s = t % 60
timer = "{:02d}:{:02d}:{:02d}".format(h,m,s)
my_label.setText(timer)
sleep(1)
t -= 1
app = Qtw.QApplication([])
window = MainWindow()
app.exec_()
The way the code is written in the OP, it doesn't really get stuck or frozen. But rather, the display fails to update. You can get around this by trying to force Qt to update the GUI with app.processEvents(). Put it in your setting_label function after the setText():
self.my_label.setText(timer)
app.processEvents()
The Preferred Way
Using app.processEvents() a lot is usually discouraged. Another way to make a countdown timer is to use a QTimer. There is a little more overhead in setting up all of the signals and slots. But it can be very powerful. Here is an example
from time import sleep
import PyQt5.QtWidgets as Qtw
from PyQt5.QtCore import QTimer
class MainWindow(Qtw.QWidget):
def __init__(self):
super().__init__()
self.setLayout(Qtw.QVBoxLayout())
pushButton = Qtw.QPushButton("start",clicked = self.start_countdown)
self.layout().addWidget(pushButton)
self.my_label = Qtw.QLabel("00:00:00")
self.layout().addWidget(self.my_label)
self.timer = QTimer() # create a new QTimer instance
self.timer.setInterval(1000) # make it fire every 1000 msec
self.t = 1200
self.timer.timeout.connect(self.setting_label) # connect the timeout signal to self.setting_label
self.show()
def start_countdown(self):
self.t = 1200
self.timer.start()
def setting_label(self):
if self.t == 0:
self.timer.stop()
print('timer stopped')
h = self.t // 3600
m = self.t // 60
s = self.t % 60
timer = "{:02d}:{:02d}:{:02d}".format(h,m,s)
self.my_label.setText(timer)
self.t -= 1
app = Qtw.QApplication([])
window = MainWindow()
app.exec_()
Edit: This solution works for the OP, but others have pointed out that doing this using threading can cause unexpected behavior, crashes, etc. so it's better to do this using other methods (for example: this answer)
The interface freezes because the while loop you use to change the label blocks the event loop of the app (i.e. receiving inputs). You can fix this by moving the function to a thread like this:
import threading
from time import sleep
import PyQt5.QtWidgets as Qtw
class MainWindow(Qtw.QWidget):
def __init__(self):
super().__init__()
self.setLayout(Qtw.QVBoxLayout())
def setting_label():
t = 1200
while t:
h = t // 3600
m = t // 60
s = t % 60
timer = "{:02d}:{:02d}:{:02d}".format(h,m,s)
my_label.setText(timer)
sleep(1)
t -= 1
pushButton = Qtw.QPushButton(
"start",
clicked=lambda: thread.Thread(target=setting_label).start()
)
self.layout().addWidget(pushButton)
my_label = Qtw.QLabel("00:00:00")
self.layout().addWidget(my_label)
self.show()
app = Qtw.QApplication([])
window = MainWindow()
app.exec_()

PyQT5 - Add line one by one using a pause

The following code draw single random lines every merry one second. What I would like to do is to keep each line already drawn. What is the best way to do that ?
I know that I need to use a QTimer to do a responsive user interface but first I need to know how to draw more and more lines...
Maybe one way would be to draw all lines hidden and to show more and more lines... Or must I use a QGraphicsView ?
from random import random
import sys
from time import sleep
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter
from PyQt5.QtCore import QTimer
LINES = [
(500*random(), 500*random(), 500*random(), 500*random())
for _ in range(50)
]
class Interface(QWidget):
def __init__(self):
super().__init__()
self.max = len(LINES)
self.cursor = 0
self.painter = QPainter()
self.setFixedSize(500, 500)
self.show()
def paintEvent(self, e):
self.painter.begin(self)
self.drawsetpbystep()
self.painter.end()
def drawsetpbystep(self):
if self.cursor < self.max:
self.painter.drawLine(*LINES[self.cursor])
self.update()
sleep(0.25)
self.cursor += 1
if __name__ == '__main__':
app = QApplication(sys.argv)
interface = Interface()
sys.exit(app.exec_())
It is not recommended to use sleep in a GUI, and in the case of PyQt it is very dangerous, because Qt offers alternatives to create the same effect as QTimer, QEventLoop, etc.
Another error is that the QPainter has a very large life cycle, it should only be created and called in paintEvent.
And the last mistake is wanting to pause the task of paintEvent since you're doing it through the drawsetpbystep method. the paintEvent method not only will you use it but actually uses the application whenever you need it, the right thing to do is use a flag to indicate when you should paint as shown below:
LINES = [
(500*random(), 500*random(), 500*random(), 500*random())
for _ in range(50)
]
class Interface(QWidget):
def __init__(self):
super().__init__()
self.max = len(LINES)
self.cursor = 0
self.show()
self.paint = False
timer = QTimer(self)
timer.timeout.connect(self.onTimeout)
timer.start(250)
def paintEvent(self, e):
painter = QPainter(self)
if self.paint:
self.drawsetpbystep(painter)
def onTimeout(self):
self.paint = True
self.update()
def drawsetpbystep(self, painter):
if self.cursor < self.max:
painter.drawLine(*LINES[self.cursor])
self.cursor += 1
self.paint = False
if __name__ == '__main__':
app = QApplication(sys.argv)
interface = Interface()
sys.exit(app.exec_())
Using time.sleep in PyQt applications is not recommended because it blocks execution of the Qt event loop which is responsible for handling user input (via keyboard and mouse) and actually drawing the application window.
Instead, you should use QTimer to schedule execution of a specified method at the times you want. In this case, you probably want to use multiple QTimer.singleShot calls. Likely the first method called by the timer will draw one point/line and then set up a timer to call another method which will draw one point/line and set up a timer to call another method...etc. etc.

Using QTimer.singleShot correctly

I have the following PySide application, where the intended functionality is to have the text of the number_button be updated every 5 seconds, counting from 0 to 9 once the start_button has been pressed.
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self, parent=None):
super(Example, self).__init__(parent)
self.app_layout = QtGui.QVBoxLayout()
self.setLayout(self.app_layout)
self.setGeometry(300, 300, 50, 50)
self.count_to = 10
self.delay = 5000
self.timer = QtCore.QTimer(self)
self.timer.setSingleShot(True)
# start button
start_button = QtGui.QPushButton()
start_button.setText('START')
start_button.clicked.connect(self.startCount)
self.app_layout.addWidget(start_button)
# number button
self.number_button = QtGui.QPushButton()
self.number_button.setText('0')
self.app_layout.addWidget(self.number_button)
def startCount(self):
def updateButtonCount():
self.number_button.setText("%s" % count)
for count in range(0, self.count_to):
self.timer.singleShot(self.delay, updateButtonCount)
def main():
app = QtGui.QApplication(sys.argv)
example = Example()
example.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
However, this results in a 9 appearing after 6 seconds without the intermediate numbers showing at all. I'm pretty sure that the problem is that while the .singleShot is running, count has already been incremented to its maximum value (9).
I can think of a few hack to make this work as intended, but I'd like to fix it in the most effective and appropriate way.
As mentioned in the QTimer PySide documentation, what you need is a QTimer that will repeatedly time out (every 5 seconds in your case) and call the function updateButtonCount once for every timeout - as mentioned by aruisdante. Take a look at this:
timer = QTimer() # set up your QTimer
timer.timeout.connect(self.updateButtonCount) # connect it to your update function
timer.start(5000) # set it to timeout in 5000 ms
With some modifications, the previous code should help you achieve the functionality you want. Keep in mind, timer.start(5000) only sets up one timeout to occur in 5000 ms, or 5 seconds, and your updateButtonCount function should include this line at the end if the QTimer is to timeout again.
I hope that helps. Feel free to comment if something is not clear.

Update time for recording

I doing a simple python GUI using tkinter to do screen recording.Basically, I am using ffmpeg commands at the backend with tkinter as the front end triggering the ffmpeg commands.There is something that I stuck with.I dont know why my time is unable to trigger off if I program in this way.
The code below is basically the recording method.You will notice that I am actually trying to update my tkinter GUI in the while loop.This method is actually in my class named Gui_Rec() which contains other methods I need for my screen recording program.
def rec(self):
global videoFile
mydate = datetime.datetime.now()
videoFile = mydate.strftime("\%d%b_%Hh%Mm.avi")
self.l['text']=os.path.expanduser('~')+"\Videos"
self.l1['text']=videoFile
self.b.config(state=DISABLED)
self.b1.config(state=ACTIVE)
t = Thread(target=self.rec_thread)#trigger another method using thread which will run ffmpeg commands here
t.start()
while True:
if self.count_flag == False:
break
self.label['text'] = str("%02dm:%02ds" % (self.mins,self.secs))
if self.secs == 0:
time.sleep(0)
else:
time.sleep(1)
if(self.mins==0 and self.secs==1):
self.b1.config(fg="white")
self.b1.config(bg="red")
self.b.config(fg="white")
self.b.config(bg="white")
if self.secs==60:
self.secs=0
self.mins+=1
self.label['text'] = str("%02dm:%02ds" % (self.mins,self.secs))
main.gui.update()
self.secs = self.secs+1
other method in the class Gui_Rec() then this below
def main():
gui = Gui_Rec()
gui.minsize(300,155)
gui.maxsize(390,195)
gui.title("Desktop REC")
gui.attributes("-topmost", 1)
gui.mainloop() #start mainloop of program
if __name__ == '__main__':
main()
Strangely, if I don't put the above section of code in the the def main(), the GUI will be update with the duration of the time running when rec button is pressed.I don't really know how to go about solving this.Tried putting it in another thread yet it doesn't work as well.Thank you everyone for your help.
The while loop is creating a conflict with Tkinter's mainloop. Threading or multiprocessing are solutions, but I'd recommend looking into Tkinter's after() method. Here's a simplified example of how to handle a timer using after:
from Tkinter import *
class App(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.mins = 0
self.secs = 0
# make a stringvar instance to hold the time
self.timer = StringVar()
self.timer.set('%d:%d' % (self.mins, self.secs))
Label(self, textvariable=self.timer).pack()
Button(self, text='Start', command=self._start_timer).pack()
Button(self, text='Stop', command=self._stop_timer).pack()
def _start_timer(self):
self.secs += 1 # increment seconds
if self.secs == 60: # at every minute,
self.secs = 0 # reset seconds
self.mins += 1 # and increment minutes
self.timer.set('%d:%d' % (self.mins, self.secs))
# set up the after method to repeat this method
# every 1000 ms (1 second)
self.repeater = self.after(1000, self._start_timer)
def _stop_timer(self):
self.after_cancel(self.repeater)
root = Tk()
App(root).pack()
mainloop()

PySide QWidget immediate update

In my application, I have a call to an external module which spawns some threads, does some stuff, then returns a value. I'm trying to get a QMessageBox to show before and a QLabel to update after this is complete, but I'm stumped. The code goes something like this (called from QObject.connect on a button):
def _process(self):
self._message_box.show()
for i in range(3):
rv = external_module_function_with_threads() // blocking function call
label = getattr(self, "label%d" % (i + 1))
label.setText(rv)
When I click the button and the function is called, the message box only shows after the loop completes. The labels only update after the loop completes as well. I tried calling label.repaint() in the loop, but all that seems to do is make the message box show up earlier (but with no text in it).
I know I'm not violating the "GUI operations from outside the main thread" rule (...right?), so is there a way to force an update?
For your message box use self._message_box.exec_(). From my understanding of your question, I think this will do what you want.
from PySide.QtCore import *
from PySide.QtGui import *
import sys
import time
class Main(QWidget):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
layout = QVBoxLayout(self)
button = QPushButton("Press me")
self.label = QLabel("Run #")
map(layout.addWidget, [button, self.label])
button.pressed.connect(self.buttonPressed)
self.messageBox = QMessageBox()
def buttonPressed(self):
self.messageBox.exec_()
Thread().run(self.label)
class Thread(QThread):
def run(self, label):
for x in range(5):
self.updateLabel(label)
app.processEvents()
time.sleep(.5)
def updateLabel(self, label):
try:
number = int(label.text().split(" ")[-1])
number += 1
except ValueError:
number = 0
label.setText("Run %i" % number)
app = QApplication([])
main = Main()
main.show()
sys.exit(app.exec_())

Categories

Resources