Using QTimer.singleShot correctly - python

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.

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

Change Stacked Widget index automatically after 3 seconds in PyQt5

I am trying to display the first page of the stacked widget ( the welcome page ) and after 3 seconds replace it by the second page ( the Menu page ) automatically.
I tried this approach but it doesn't work..
..........
self.stackedWidget.setCurrentIndex(0)
time.sleep(3)
self.stackedWidget.setCurrentIndex(1)
QtCore.QMetaObject.connectSlotsByName(smartUpdaterUI)
..............
Qt uses a GUI event loop to handle events, and update the UI. Any updates to the GUI won't be visible until control is handed over to the event loop and that update is picked up and processed.
The code you write in Python happens in the same thread as the GUI. So while your code is running, the event loop is not, and changes are not being processed.
Side note: this is why your application can 'hang' if you try and do something long-running like accessing an API, without using a separate thread.
In your code, you're setting the current index to 0, then using Python time.sleep() to wait, before updating to index 1.
self.stackedWidget.setCurrentIndex(0)
time.sleep(3) # no event loop running here
self.stackedWidget.setCurrentIndex(1)
While the time.sleep(3) is happening, the execution is held at this point. This means control is not handed back to the Qt event loop, and the first change is not processed. Once the timeout completes, the second index is set, and only then your function returns control back to Qt.
The event loop now applies both changes, but immediately one after another. So all you see is index being set to 1, without first showing 0 and without any delay.
To avoid this, you need to return control back to the event loop after setting the initial index. The simplest way to do this is to just set it, and then trigger the subsequent update using an asynchronous QTimer.
self.stackedWidget.setCurrentIndex(0)
QTimer.singleShot(3000, lambda: self.stackedWidget.setCurrentIndex(1))
The lambda: is used as an anonymous function, so we can pass 1 to setCurrentIndex by delaying execution until the timer is triggered. If you're only ever jumping to a single page you could do this instead:
def go_to_page_1(self):
self.stackedWidget.setCurrentIndex(1)
self.stackedWidget.setCurrentIndex(0)
QTimer.singleShot(3000, go_to_page_1)
void QTimer::singleShot(int msec, const QObject *receiver, const char *member)
This static function calls a slot after a given time interval.
Try it:
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class stackedExample(QWidget):
def __init__(self):
super(stackedExample, self).__init__()
self.leftlist = QListWidget()
self.leftlist.insertItem (0, 'Contact' )
self.leftlist.insertItem (1, 'Personal' )
self.leftlist.insertItem (2, 'Educational' )
self.stack1 = QWidget()
self.stack2 = QWidget()
self.stack3 = QWidget()
self.stack1UI()
self.stack2UI()
self.stack3UI()
self.stackWidget = QStackedWidget(self)
self.stackWidget.addWidget(self.stack1)
self.stackWidget.addWidget(self.stack2)
self.stackWidget.addWidget(self.stack3)
hbox = QHBoxLayout(self)
hbox.addWidget(self.leftlist)
hbox.addWidget(self.stackWidget)
self.setLayout(hbox)
self.leftlist.currentRowChanged.connect(self.display)
self.setGeometry(300, 50, 10, 10)
self.setWindowTitle('StackedWidget demo')
self.stackWidget.setCurrentIndex(0)
QTimer.singleShot(3000, lambda: self.display(1))
QTimer.singleShot(6000, lambda: self.display(2))
QTimer.singleShot(9000, lambda: self.display(0))
self.show()
def stack1UI(self):
layout = QFormLayout()
layout.addRow("Name", QLineEdit())
layout.addRow("Address", QLineEdit())
#self.setTabText(0,"Contact Details")
self.stack1.setLayout(layout)
def stack2UI(self):
layout = QFormLayout()
sex = QHBoxLayout()
sex.addWidget(QRadioButton("Male"))
sex.addWidget(QRadioButton("Female"))
layout.addRow(QLabel("Sex"),sex)
layout.addRow("Date of Birth",QLineEdit())
self.stack2.setLayout(layout)
def stack3UI(self):
layout = QHBoxLayout()
layout.addWidget(QLabel("subjects"))
layout.addWidget(QCheckBox("Physics"))
layout.addWidget(QCheckBox("Maths"))
self.stack3.setLayout(layout)
def display(self, i):
self.stackWidget.setCurrentIndex(i)
def main():
app = QApplication(sys.argv)
ex = stackedExample()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

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.

Can't seem to get pyqt countdown timer to work

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)

Python PyQt4 progressbar Freezing GUI

the following code counts a bunch of PyQt4 progressbar's up tp 99%, I would like for my GUI NOT to freeze as they count up to 99%. I would LOVE to do this without custom classes or functions if it is possible.
I know it is good to use classes but for this tiny snippet of code I don't want to create a class.
From what I've read there may be a update() function that could accomplish this ... please advise If I'm on the right track
import sys
import time
from PyQt4 import QtGui
from PyQt4 import QtCore
app = QtGui.QApplication(sys.argv)
widget = QtGui.QWidget()
widget.resize(400, 200)
widget.setWindowTitle('simple')
widget.show()
shift = 0
cntControl = 5
barra = [0] * cntControl
for i in range(cntControl):
shift = shift + 10
barra[i] = QtGui.QProgressBar(widget)
barra[i].show()
barra[i].setMinimum(0)
barra[i].setMaximum(10000)
barra[i].setGeometry(QtCore.QRect(10, shift, 200, 10))
for a in range(10000):
for i in range(cntControl):
barra[i].setValue(a)
sys.exit(app.exec_())
try changing your for loop with:
while True:
for a in range(10000):
time.sleep(0.0001)
for i in range(cntControl):
barra[i].setValue(a)
if works for me.
The while loop continues endlessly moving the bar. If you are looking only to clean the bar after it reaches the end you should use reset:
PySide.QtGui.QProgressBar.reset()
Reset the progress bar. The progress bar “rewinds” and shows no
progress
Update after OP comments: If you want your gui to be responsive when entering a long loop or other operation you should use either python thread module or QThreads.
I really could not get threads to work at all ... I could post my thread attempt which looks flawless to my (now tired) eyes ...
I have however been able to tweek http://zetcode.com/tutorials/pyqt4/widgets/ 's progressbar example and came out with the following code ... which solves the problem of freezing in the GUI:
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.setWindowTitle('ProgressBar')
self.setGeometry(300, 300, 250, 150)
self.pbar = [0] * 3
self.timer = [0] * 3
self.step = [0] * 3
self.shift = 0
for i in range(3):
self.shift = self.shift + 30
self.pbar[i] = QtGui.QProgressBar(self)
self.pbar[i].setGeometry(30, self.shift, 200, 25)
self.timer[i] = QtCore.QBasicTimer()
self.step[i] = 0
self.timer[i].start(100, self)
def timerEvent(self, event):
for i in range(3):
if self.step[i] >= 100:
self.timer[i].stop()
return
self.step[i] = self.step[i] + 1
self.pbar[i].setValue(self.step[i])
app = QtGui.QApplication(sys.argv) ex
= Example() ex.show() app.exec_()
I have NO IDEA why it works which is probably not a good thing. I'm guessing it might have something to do with super(Example, self).__init__() and the custom timer pyqt4 uses. I was really hoping to do this without functions or classes, but not sure that is possible. If you think it is, feel free to post!

Categories

Resources