Stopwatch (chronometre) doesn't work - python

I make a stopwatch (chronometre) in python using the library pyqt, but I have a problem when I press the reset button and then the start button, I see the time increase of two, and repeat the process I see that increase in threes and so on.
I do not understand why this happens. I hope some can help me.
This is the code:
# -*- coding: utf-8 -*-
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4 import uic
class Cronometro(QWidget):
def __init__(self):
QWidget.__init__(self)
uic.loadUi("cronometro.ui", self)
self.sec = 0
self.timer = QTimer()
self.set_time()
# Conexion
self.btnStart.clicked.connect(self.start)
self.btnReset.clicked.connect(self.reset)
self.btnExit.clicked.connect(self.close)
def start(self):
self.timer.timeout.connect(self.counter)
self.timer.start(1000)
def reset(self):
self.timer.stop()
self.sec = 0
def counter(self):
self.sec += 1
self.set_time()
def is_timer_active(self):
return self.timer.isActive()
def set_time(self):
hora = self.sec / 3600
minutos = (self.sec % 3600) / 60
segundos = (self.sec % 3600) % 60
self.label.setText("%02d:%02d:%02d" % (hora, minutos, segundos))
app = QApplication(sys.argv)
ventana = Cronometro()
ventana.show()
sys.exit(app.exec_())
Thank you very much !!!

The problem is here, in your start method:
def start(self):
self.timer.timeout.connect(self.counter)
self.timer.start(1000)
Every time you start the timer, you connect the timer's timeout signal to your counter method. So if you start the timer twice, there are two connections from the timer to your counter method, so the counter method gets called twice on every tick of the timer.
The fix is fairly straightforward: move the line
self.timer.timeout.connect(self.counter)
into your __init__ method, so that the connection is only ever made once.

Related

How to make QProgressBar display updates while another procedure is running in pyqt

I want to be able to show the progress of a procedure while it is running. My problem is that the QProgressBar seems to be frozen while the procedure is running. Below is an example that shows that while execute_procedure is running (first 5 seconds), the progress_bar doesn't visually update even though the thread is emitting.
from PyQt5 import QtWidgets, QtCore
import sys
import time
class ProgressThread(QtCore.QThread):
progress_changed = QtCore.pyqtSignal(int)
def __init__(self):
super().__init__()
def run(self):
completed = 0
max_time = 15
increment = float(100 / max_time)
while completed < 100:
completed += increment
time.sleep(1)
self.progress_changed.emit(completed)
# print('emitted ', completed) -- this is emitting while
# execute_procedure is running but the progress bar
# is not visually updating until the procedure is completed
class TestProgress(QtWidgets.QDialog):
def __init__(self):
super().__init__()
self.tab = QtWidgets.QWidget()
self.tab.setGeometry(300, 300, 1000, 200)
self.button_execute = QtWidgets.QPushButton("Execute", self.tab)
self.button_execute.clicked.connect(self.on_click)
self.progress_bar = QtWidgets.QProgressBar(self.tab)
self.progress_bar.move(0, 40)
self.progress_bar.setProperty("value", 100)
self.worker = ProgressThread()
self.worker.progress_changed.connect(self.update_progress_bar)
self.tab.show()
def on_click(self):
self.worker.start()
self.execute_procedure()
def execute_procedure(self):
# while this procedure is running, the progress bar is not updating
cnt = 0
while cnt <= 5:
cnt += 1
time.sleep(1)
def update_progress_bar(self, value):
self.progress_bar.setValue(value)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = TestProgress()
sys.exit(app.exec_())
You cannot and should not execute a blocking task in the main thread since it prevents the GUI from executing its task correctly.
There are 2 options depending on the use of time.sleep:
If the task of time.sleep() is just to give a delay T seconds then use a QTimer.
# ...
class TestProgress(QtWidgets.QDialog):
# ...
def execute_procedure(self):
self.counter = 0
QtCore.QTimer.singleShot(1000, self.execute_counter)
def execute_counter(self):
if self.counter <= 5:
self.counter += 1
QtCore.QTimer.singleShot(1000, self.execute_counter)
# ...
If in case time.sleep() emulates a task that consumes T seconds then execute it in another thread.
import threading
# ...
class TestProgress(QtWidgets.QDialog):
# ...
def on_click(self):
self.worker.start()
threading.Thread(target=self.execute_procedure, daemon=True).start()
# ...

PyQt5 threading GUI does not work

I am trying to load some data which takes 30+ seconds. During this time I wish the user to see a small GUI which says "Loading .", then "Loading ..", then "Loading ...", then "Loading ." etc. I have done some reading and I think I have to put this in a separate thread. I found someone who had a similar problem suggesting the solution was this in the right spot:
t = threading.Thread(target=self.test)
t.daemon = True
t.start()
In a lower part of the file I have the test function
def test(self):
tmp = InfoMessage()
while True:
print(1)
and the InfoMessage function
from PyQt5 import uic, QtCore, QtGui, QtWidgets
import sys
class InfoMessage(QtWidgets.QDialog):
def __init__(self, msg='Loading ', parent=None):
try:
super(InfoMessage, self).__init__(parent)
uic.loadUi('ui files/InfoMessage.ui',self)
self.setWindowTitle(' ')
self.o_msg = msg
self.msg = msg
self.info_label.setText(msg)
self.val = 0
self.timer = QtCore.QTimer()
self.timer.setInterval(500)
self.timer.timeout.connect(self.update_message)
self.timer.start()
self.show()
except BaseException as e:
print(str(e))
def update_message(self):
self.val += 1
self.msg += '.'
if self.val < 20:
self.info_label.setText(self.msg)
else:
self.val = 0
self.msg = self.o_msg
QtWidgets.QApplication.processEvents()
def main():
app = QtWidgets.QApplication(sys.argv) # A new instance of QApplication
form = InfoMessage('Loading ') # We set the form to be our MainWindow (design)
app.exec_() # and execute the app
if __name__ == '__main__': # if we're running file directly and not importing it
main() # run the main function
When I run the InfoMessage function alone it works fine and it updates every 0.5 seconds etc. However, when I fun this as part of the loading file the GUI is blank and incorrectly displayed. I know it is staying in the test function because of the print statement in there.
Can someone point me in the right direction? I think I am missing a couple of steps.
First, there are two ways of doing this. One way is to use the Python builtin threading module. The other way is to use the QThread library which is much more integrated with PyQT. Normally, I would recommend using QThread to do threading in PyQt. But QThread is only needed when there is any interaction with PyQt.
Second, I've removed processEvents() from InfoMessage because it does not serve any purpose in your particular case.
Finally, setting your thread as daemon implies your thread will never stop. This is not the case for most functions.
import sys
import threading
import time
from PyQt5 import uic, QtCore, QtWidgets
from PyQt5.QtCore import QThread
def long_task(limit=None, callback=None):
"""
Any long running task that does not interact with the GUI.
For instance, external libraries, opening files etc..
"""
for i in range(limit):
time.sleep(1)
print(i)
if callback is not None:
callback.loading_stop()
class LongRunning(QThread):
"""
This class is not required if you're using the builtin
version of threading.
"""
def __init__(self, limit):
super().__init__()
self.limit = limit
def run(self):
"""This overrides a default run function."""
long_task(self.limit)
class InfoMessage(QtWidgets.QDialog):
def __init__(self, msg='Loading ', parent=None):
super(InfoMessage, self).__init__(parent)
uic.loadUi('loading.ui', self)
# Initialize Values
self.o_msg = msg
self.msg = msg
self.val = 0
self.info_label.setText(msg)
self.show()
self.timer = QtCore.QTimer()
self.timer.setInterval(500)
self.timer.timeout.connect(self.update_message)
self.timer.start()
def update_message(self):
self.val += 1
self.msg += '.'
if self.val < 20:
self.info_label.setText(self.msg)
else:
self.val = 0
self.msg = self.o_msg
def loading_stop(self):
self.timer.stop()
self.info_label.setText("Done")
class MainDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
# QThread Version - Safe to use
self.my_thread = LongRunning(limit=10)
self.my_thread.start()
self.my_loader = InfoMessage('Loading ')
self.my_thread.finished.connect(self.my_loader.loading_stop)
# Builtin Threading - Blocking - Do not use
# self.my_thread = threading.Thread(
# target=long_task,
# kwargs={'limit': 10}
# )
# self.my_thread.start()
# self.my_loader = InfoMessage('Loading ')
# self.my_thread.join() # Code blocks here
# self.my_loader.loading_stop()
# Builtin Threading - Callback - Use with caution
# self.my_loader = InfoMessage('Loading ')
# self.my_thread = threading.Thread(
# target=long_task,
# kwargs={'limit': 10,
# 'callback': self.my_loader}
# )
# self.my_thread.start()
def main():
app = QtWidgets.QApplication(sys.argv)
dialog = MainDialog()
app.exec_()
if __name__ == '__main__':
main()
Feel free to ask any follow up questions regarding this code.
Good Luck.
Edit:
Updated to show how to run code on thread completion. Notice the new parameter added to long_task function.

Update QWidget every minutes

Is their a way possible to update a Qwidget in PyQT4 every 15 minutes ? I know their is something like a Qtimer but is it also possible to make a QWidget update itself at a specific time,for example 00h00 00h15 00h30 00h45 01h00,... . So is their a way to make the Qtimer depend on the time?
The QTimer class has a setInterval method. You can utilize this to change the wait time on the fly. As a short example, this block of code will show the current second. However, if the second is a multiple of 10 it will wait 5 seconds before starting again:
import sys
from PyQt4 import QtGui, QtCore
from time import strftime
class Main(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.initUI()
def initUI(self):
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.Time)
self.timer.start(1000)
self.lcd = QtGui.QLCDNumber(self)
self.lcd.display(strftime("%S"))
self.setCentralWidget(self.lcd)
self.setGeometry(300,300,250,100)
def Time(self):
if int(strftime("%S")) % 10 == 0:
self.timer.setInterval(5000)
else:
self.timer.setInterval(1000)
self.lcd.display(strftime("%S"))
def main():
app = QtGui.QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
(This was modified slightly from a tutorial showing how to build a digital clock)
It looks like this for 4 consecutive changes of the display:
For your particular problem, when you start the application, you can find how long it is until the next quarter hour via a function like is presented in this answer:
def next_quarter_hour(self):
dt = datetime.datetime.now()
nsecs = dt.minute*60+dt.second+dt.microsecond*1e-6
delta = (nsecs//900)*900+900-nsecs
return delta * 1000.0
This would change the following:
def initUI(self):
...
self.timer.start(next_qt_hour)
...
def Time(self):
if int(strftime("%M")) % 15 == 0:
self.timer.setInterval(900000)
self.lcd.display(strftime("%M"))
Use this to set your first interval. Then once that timer has been exhausted, reset your interval to 15 minutes.

QThread Mystery Error

I'm currently attempting to create a threaded timer application using PyQt. Simple, right? I thought so too. However, after spending all day trying to figure out what's going wrong, I still have absolutely no idea. In all of my immense stubbornness, I have refused to give up on what was supposed to be a 15-minute project.
Heres mah codez:
__author__ = 'Darth Vader'
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMessageBox, QApplication, QDialog
from PyQt5.QtCore import QThread
from timerui import Ui_Form
import sys
import ctypes
import time
import threading
class Timer(QThread):
def makeNoise(self):
pass
def run(self):
self.ui.startButton.setStyleSheet('''QPushButton {color: red;font: bold 15px;}''')
self.ui.startButton.setEnabled(False)
self.hour = int(self.ui.spinBoxHrs.value())
self.min = int(self.ui.spinBoxMin.value())
self.sec = int(self.ui.spinBoxSec.value())
if self.sec:
self.countSec = self.sec
elif self.min and not self.sec:
self.countSec = 60
self.min -= 1
elif self.hour and not self.min and not self.sec:
self.min = 59
self.countSec = 60
print(self.countSec)
while self.countSec or self.hour or self.min:
if not self.countSec and self.min:
self.min -= 1
self.countSec = 60
elif not self.countSec and not self.min and self.hour:
self.hour -= 1
self.min = 59
self.sec = 60
elif not self.countSec and not self.min and not self.hour:
self.makeNoise()
break
time.sleep(1)
self.countSec -= 1
self.ui.startButton.setText("%dh %dm %ds" % (self.hour, self.min, self.sec))
self.ui.startButton.setEnabled(True)
self.ui.startButton.setText("Start")
self.ui.startButton.setStyleSheet('QPushButton{}')
def setup(self, gui):
self.ui = gui
def __init__(self):
QThread.__init__(self)
def start():
t = Timer()
t.start()
if __name__ == '__main__':
myappid = u'org.ayoung.timer'
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
app = QApplication(sys.argv)
app.setWindowIcon(QtGui.QIcon('res/favicon.png'))
window = QDialog()
ui = Ui_Form()
ui.setupUi(window)
ui.startButton.clicked.connect(start)
window.show()
sys.exit(app.exec_())
And the error:
QThread: Destroyed while thread is still running
QMutex: destroying locked mutex
From what I've read, these two errors have something to do with garbage collection, but I have absolutely no idea how to fix them.
Thanks!
There are three issues with your code. You need to solve the first one before you can solve the third one (the second issue should go away once you solve the first issue)
You are confusing functions and methods. I can tell this because you have a function called start, but the fact that the first argument in the function signature is called self indicates that you want it to be a method of an object. You might want to read this for a good explanation of the difference between a function and a method.
As a consequence of the previous point, and the fact that the QPushButton.clicked signal emits a boolean, means that when start is called, the self variable contains True (rather than a reference to the instance of a class (otherwise known as an object) - which is what would happen is start was a method rather than a function)
The line self.t = Timer().start() does the following:
Timer() creates an instance of the Timer class
The start method of this instance is called
The return value of the start() method is stored in self.t (we're ignoring here the problem that self is not a reference to an object).
What you want to do is instead create the instance of Timer and assign it to self.t. Then call start() on self.t. For example:
self.t = Timer()
self.t.start()
This ensures that the Timer object is saved somewhere and does not get garbage collected.
def start(self):
self.t = Timer().start()
...
ui.startButton.clicked.connect(start)
Here you are connecting a signal to a slot. The parameter passed to the slot is the button state, which is a bool. So self is True in your case.

nonblocking timer in python

I am developing an app in python with pyGtk+Glade.
I want to execute a function every N seconds (just like in javascript the function setTimeout()). If the user perform a click, some action must be done, and the timer must be reset.
I was trying with threading.Timer, something like this:
def callback():
print "callback executed"
t = threading.Timer(10.0, callback)
t.start()
but it doesn't work for me because it blocks everything for the N secs and doesn't capture the user click.
Any other option?
Since you're using PyGTK, your program should probably be using the g_main_loop, in which case you can call glib.timeout_add (interval, callback) to add a callback that gets called (roughly) every X seconds.
Here's an example:
import glib
import gtk
def yo ():
print "yo"
return True
glib.timeout_add (1000, yo)
gtk.main ()
Unfortunately, to reset the timeout I couldn't come up with an elegant solution. But you can create your own main loop so you have control over when the timeout resets, sorta like this:
import glib
import gtk
import time
timeout = 1;
timer = time.time() + timeout
while (True):
gtk.main_iteration (False)
if (timer <= time.time()):
print "Time up!"
timer = time.time() + timeout
This creates a timer which calls MainWin.update() every second. When the button is pressed, the current timer is killed and a new timer is started.
import pygtk
pygtk.require('2.0')
import gtk
import gobject
import time
class MainWin(object):
def destroy(self, widget, data=None):
gtk.main_quit()
def __init__(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.connect("destroy", self.destroy)
self.window.set_border_width(10)
self.button = gtk.Button("Reset")
self.button.connect("clicked", self.onclick)
self.window.add(self.button)
self.button.show()
self.window.show()
self.reset_timer = False
self.count = 0
self.duration = 1000
self.timer = gobject.timeout_add(self.duration, self.update)
def reset(self):
print('Resetting timer')
gobject.source_remove(self.timer)
# start a new period call to update
self.count = 0
self.timer = gobject.timeout_add(self.duration, self.update)
def onclick(self, widget):
# Every action which resets the timer should call self.reset_timer().
self.reset()
def update(self):
print('{t:.1f}: count = {i}'.format(t=time.time() % 100, i=self.count))
self.count += 1
return True
def main(self):
gtk.main()
if __name__=='__main__':
MainWin().main()

Categories