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()
Related
I am working with serial device and set a flag (which is global variable) based on the received data. Now I want to reset the flag after a while (for example one second) by using a timer.
Here is the code:
class Inlet_Worker(QObject):
def __init__(self):
super(Inlet_Worker, self).__init__()
self.timer = QTimer(self)
self.timer.timeout.connect(self.Reset_Register_Barcode)
def run(self):
global Register_Barcode
while True :
if client.read_coils(address = 0x0802).bits[0]:
Register_Barcode = True
self.timer.start(1000)
def Reset_Register_Barcode(self):
global Register_Barcode
Register_Barcode = False
However the timer is not working.
I will assume from your example code that your are using a QThread and that you also use QObject.moveToThread on your worker object. This is the correct procedure, but there are some other things you must do to make your timer work.
Firstly, you should use a single-shot timer so as to avoid re-regsitration whilst the current one is active. Secondly, you must explicitly process any pending events, since your while-loop will block the thread's event-loop. Without this, the timer's timeout signal will never be emitted. Thirdly, you should ensure that the worker and thread shut down cleanly when the program exits (which will also prevent any Qt error messages). Finally, if possible, you should use signals to communicate registration changes to the main GUI thread, rather than global variables.
The demo script below (based on your example) implements all of that. After the Start button is clicked, the thread will start and periodically update the regsitration (indicated by the check-box). Hopefully you shoudld be able to see how to adapt it to your real application:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class Inlet_Worker(QObject):
barcodeRegistered = pyqtSignal(bool)
def __init__(self):
super().__init__()
self._stopped = False
self._registered = False
self.timer = QTimer(self)
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.updateBarcodeRegistration)
def run(self):
count = 0
self._stopped = False
while not self._stopped:
#if client.read_coils(address = 0x0802).bits[0]:
count += 1
if count % 20 == 0 and not self._registered:
self.updateBarcodeRegistration(True)
self.timer.start(2000)
QCoreApplication.processEvents()
QThread.msleep(100)
self.updateBarcodeRegistration(False)
self.timer.stop()
print('Stopped')
def updateBarcodeRegistration(self, enable=False):
print('Register' if enable else 'Reset')
self._registered = enable
self.barcodeRegistered.emit(enable)
def stop(self):
self._stopped = True
class Window(QWidget):
def __init__(self):
super().__init__()
self.thread = QThread()
self.worker = Inlet_Worker()
self.worker.moveToThread(self.thread)
self.button = QPushButton('Start')
self.check = QCheckBox('Registered')
layout = QHBoxLayout(self)
layout.addWidget(self.button)
layout.addWidget(self.check)
self.thread.started.connect(self.worker.run)
self.button.clicked.connect(self.thread.start)
self.worker.barcodeRegistered.connect(self.check.setChecked)
def closeEvent(self, event):
self.worker.stop()
self.thread.quit()
self.thread.wait()
if __name__ == '__main__':
app = QApplication(['Test'])
window = Window()
window.setGeometry(600, 100, 200, 50)
window.show()
app.exec()
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()
# ...
I'm making a game where I would like a timer to be displayed on the screen after the user clicks on 'NEW GAME', to keep track of how long they've been playing. I have a class that runs the timer fine by itself, but when I incorporate it into the rest of my game and then on top of that, try to display the updated values of the timer, no values in the UI are updated and the printout of the timer doesn't even occur in the terminal. I've tried running the timer in the same thread as the game-setup process and I've also tried creating a new thread to run the timer but neither work. The game loads up and functions fine, with the exception of the timer not counting upwards and not displaying the updated timer values. Where am I going wrong here?
Here is my standalone Timer class, which again, works fine by itself.
from PyQt5 import QtCore
import sys
def startThread(functionName, *args, **kwargs):
print(args)
if len(args) == 0:
t = threading.Thread(target=functionName)
else:
try:
t = threading.Thread(target=functionName, args=args, kwargs=kwargs)
except:
try:
if args is None:
t = threading.Thread(target=functionName, kwargs=kwargs)
except:
t = threading.Thread(target=functionName)
t.daemon = True
t.start()
class Timer(object):
def __init__(self):
super(Timer, self).__init__()
def start_timer(self):
print("Starting timer...")
Timer.timer = QtCore.QTimer()
Timer.time = QtCore.QTime(0, 0, 0)
Timer.timer.timeout.connect(self.tick)
Timer.timer.start(1000)
def tick(self):
Timer.time = Timer.time.addSecs(1)
self.update_UI('%s' % Timer.time.toString("hh:mm:ss"))
def update_UI(self, text_string):
print(text_string)
# This is where the text would be sent to try and update the UI
Timer().start_timer()
This is more or less how my game-setup class is structured - currently I'm showing the version that uses threading:
class BuildUI(PyQt5.QtWidgets.QMainWindow, Ui_game):
def __init__(self):
super(BuildUI, self).__init__()
self.setupUi(self)
self.easy_mode = 38
self.user_available_cells = []
self.start_easy_game.triggered.connect(lambda: self.setup_game(self.easy_mode))
def setup_game(self, hidden_count):
def create_game_board():
startThread(Timer().start_timer)
self.game = BuildGame()
#The BuildGame class is not deliberately not shown
startThread(create_game_board)
class GAME(object):
def __init__(self):
GAME.app = PyQt5.QtWidgets.QApplication(sys.argv)
GAME.UI = BuildUI()
GAME.UI.show()
GAME.app.exec_()
def main():
GAME()
if __name__ == '__main__':
main()
The key to getting this to work is by using signals. Leaving the Timer class exactly as it is, the only modifications to be done are in the initialization of the GAME class, a signal needs to be added at the beginning of the BuildUI class and then using emit() to trigger that signal just before the self.game = BuildGame() call.
class BuildUI(PyQt5.QtWidgets.QMainWindow, sudoku_ui.Ui_sudoku_game):
# This signal just triggers a msgbox to display, telling the user the game is loading
process_start = PyQt5.QtCore.pyqtSignal()
# this is called to automatically close the msgbox window
process_finished = PyQt5.QtCore.pyqtSignal()
# This signal, when called will start the timer
start_game_timer = PyQt5.QtCore.pyqtSignal()
def __init__(self):
super(BuildUI, self).__init__()
self.setupUi(self)
self.easy_mode = 38
self.user_available_cells = []
self.start_easy_game.triggered.connect(lambda: self.setup_game(self.easy_mode))
def setup_game(self, hidden_count):
def create_game_board():
self.game = BuildGame()
# Now that the game is built, the timer can start
# This is the emit which will start the timer
GAME.UI.start_game_timer.emit()
startThread(create_game_board)
class GAME(object):
def __init__(self):
GAME.app = PyQt5.QtWidgets.QApplication(sys.argv)
GAME.UI = BuildUI()
GAME.dialog_box = MsgPrompt()
# This is the key right here - initializing the timer class
GAME.timer = Timer()
# This line below attaches a function to the emit() call - which will kick off the timer
GAME.UI.start_game_timer.connect(GAME.timer.start_timer)
# The below referenced class is deliberately omitted from this post
GAME.UI.process_start.connect(GAME.dialog_box.show_dialog_box)
GAME.UI.process_finished.connect(GAME.dialog_box.hide_dialog_box)
GAME.UI.show()
GAME.app.exec_()
def main():
GAME()
if __name__ == '__main__':
main()
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.
I write some simple program with Python and PyGObject. I wanted her to inform the user about the progress through the ProgressBar. I googled that this process should be a separate thread, otherwise the interface hold on, and now have some like this for tests:
import time
import threading
from gi.repository import Gtk, GObject
GObject.threads_init()
def tread_function():
progress.set_fraction(0)
time.sleep(5)
progress.set_fraction(0.25)
time.sleep(5)
progress.set_fraction(0.5)
time.sleep(5)
progress.set_fraction(0.75)
time.sleep(5)
progress.set_fraction(1)
def clickOk(*args):
t = threading.Thread(target=tread_function)
t.start()
def clickCancel(*args):
pass
buttonOk = Gtk.Button("Start Count")
buttonOk.connect("clicked", clickOk)
buttonCancel = Gtk.Button("Cancel Count")
buttonCancel.connect("clicked", clickCancel)
progress = Gtk.ProgressBar()
progress.set_show_text(True)
vBox = Gtk.VBox()
vBox.pack_start(buttonOk, True, True, 10)
vBox.pack_start(buttonCancel, True, True, 10)
vBox.pack_start(progress, True, True, 10)
window = Gtk.Window()
window.connect('destroy', Gtk.main_quit)
window.add(vBox)
window.show_all()
Gtk.main()
Now when the interface does not hold on, I would like to give a chance user to stop the work to its full completion, if he made a mistake in settings. But I can not googled, or find in documentation how to kill a thread before its complete execution.
There is no trivial way to kill a thread in python. To get around this you need to build in your own hooks that will trigger early thread exits. A good way to do this is to use Event, which is a thread-safe trigger that can be set.
Before you do that though, you may wish to wrap up your code in some classes. Writing GUIs without classes will only cause you pain in the long term.
from threading import Event,Thread
class FakeClass(object):
def __init__(self):
self.progress = Gtk.ProgressBar()
self.progress.set_show_text(True)
self.buttonOk = Gtk.Button("Start Count")
self.buttonOk.connect("clicked", self.clickOk)
self.buttonCancel = Gtk.Button("Cancel Count")
self.buttonCancel.connect("clicked", self.clickCancel)
#create an event to trigger in your thread
self.trigger = Event()
self.t = None
#Other GTK stuff here...
def tread_function(self):
progress_fraction = 0.0
#keep looping while the trigger is not set and the
#progress is not > 1.0
while not self.trigger.is_set() and progress <= 1.0:
progress.set_fraction(progress_fraction)
time.sleep(1)
progress_fraction += 0.1
def clickOk(self, *args):
# reset the trigger
self.trigger.clear()
#launch the thread
self.t = threading.Thread(target=self.tread_function)
self.t.start()
def clickCancel(self, *args):
# set the trigger (interrupting the thread)
self.trigger.set()
# join the thread so it is not left hanging
if not self.t is None:
self.t.join()
# More other GTK stuff here...