Hello,
I am currently in the design phase of a app that will be used to time the start, stop and elapsed time of certain events as they are triggered by a user clicking on a button.
I want to have several timers (counting durations in hh:mm format) displaying their values each minute. Each of the timers will start, pause and reset based on events triggered by the user clicking on certain GUI buttons.
Coming from a vb.net event driven world I had envisioned each timer being an object with methods of aTimer.Start, aTimer.Stop, aTimer.Reset and these objects would be on a form and accumulating time and updating the display constantly.
Can someone give me some pointers on how I would approach this using Python?
Thank you,
Scott
assuming you want these timers to be non blocking? as in running one timer wont stop the program from continuing untill it has completed, and will instead happily run in the background.
if this is the case you're going to have make a multithreaded program...which can be painful to say the least in python.
I think all GUI toolkits have the concept of a timer. In tk you us "after". In Qt you use QTimer. You could use multiple threads or processes, but if this is a GUI app, use what the GUI provides.
I would have only one timer and have it process a list of event_timer. An event timer is essentially a function to execute and a time to execute. The can run periodically, checking each event timer and executing the function when the event time is reached. Another choice is to set the timer to execute when the next event is scheduled. The logic is more complicated (you need to find the next event each time an event is added or executed and use that to set the timer wait), but it may result in better precision and less overhead.
You may want two types of event timers, periodic and one shot. A periodic event executes after some delay and continues to do so until stopped. A one shot executes after some delay and removes itself from the event timer list. Another possibility is to make everything a one shot and implement a periodic event by having the callback function add a new event timer.
This is an event timer I wrote for an application that uses PySide2 (Qt GUI).
"""Event is a scheduled function call
Events are added to a list that is processed during processor free time. The start
time is compared to the current time, and the event is run if the elapsed
time is greater than the event delay.
Running an event calls the event callback with the event arguments. These
are set at creation or by calling the "connect" or "execute" methods. The
event does not run again unless the "resume method is called
"execute" is a one-time event that is removed from the event list after it
is run. Use execute to do a delayed function call. Use "start" for
events that run periodically.
"""
import time
class Event:
"""Delayed function call"""
events = []
timer = None
@classmethod
def add_event(cls, event):
"""Add event to cycle"""
if not event in cls.events:
cls.events.append(event)
@classmethod
def remove_event(cls, event):
"""Remove event from cycle"""
cls.events.remove(event)
@classmethod
def timer_tic(cls):
"""Run periodically to schedule and fire events"""
current_time = time.time()
[event.tic(current_time) for event in cls.events]
def __init__(self):
"""Initialize Event."""
self.delay = 0.0
self.callback = None
self.args = ()
self.kwargs = {}
self.time = time.time()
self.oneshot = False
self.active = False
self._resume = False
def __repr__(self):
"""Return string representation of event"""
return f'(Event {self.callback}) delay {self.delay}, active {self.active}'
def tic(self, current_time):
"""Event countdown"""
if not self._resume:
self.time = current_time
elif current_time - self.time >= self.delay:
self._resume = False
if self.callback is not None:
self.callback(*self.args, **self.kwargs)
if self.oneshot:
self.stop()
def connect(self, callback, *args, **kwargs):
"""Function to execute when event fires"""
self.callback = callback
self.args = args
self.kwargs = kwargs
return self
def start(self):
"""Add event to the cycle"""
if not self.active:
self.oneshot = False
self.active = True
Event.add_event(self)
self.resume()
return self
def stop(self):
"""Remove event from the cycle"""
if self.active:
self.active = False
self.remove_event(self)
return self
def execute(self, callback, *args, **kwargs):
"""Use event as a delayed function call. Runs once and is removed from event loop.
Option args and kwargs must be list or tuple
"""
self.callback = callback
self.args = args
self.kwargs = kwargs
self.oneshot = True
self.active = True
self.resume()
Event.add_event(self)
def resume(self):
"""Run event again"""
self.time = time.time()
self._resume = True
return self
def setdelay(self, sec):
"""Set delay between events"""
self.delay = sec
return self
def dump(self):
"""Print list of events"""
for event in self.events:
print(event)
This is the code in the main program that creates the Event timer.
event_timer = QtCore.QTimer(None)
event_timer.timeout.connect(Event.timer_tic)
event_timer.start(1)
The Gui would be responsible for calling the method
get_duration
every update interval.
import datetime
import time
class Timer:
def __init__(self):
self.timedelta = datetime.timedelta()
self.running = False
def start(self):
self.start_time = datetime.datetime.now()
self.running = True
def stop(self):
if not self.running:
return
self.timedelta += datetime.datetime.now() - self.start_time
self.running = False
def reset(self):
self.timedelta = datetime.timedelta()
self.running = False
def get_duration(self):
duration = self.timedelta
if self.running:
duration += datetime.datetime.now() - self.start_time
return duration
timer = Timer()
timer2 = Timer()
timer.start()
timer2.start()
time.sleep(1)
print(f'Timer1: {timer.get_duration()}')
print(f'Timer2: {timer2.get_duration()}')
time.sleep(1)
print(f'Timer1: {timer.get_duration()}')
print(f'Timer2: {timer2.get_duration()}')
timer2.stop()
print('Timer2 stopped')
time.sleep(1)
print(f'Timer1: {timer.get_duration()}')
print(f'Timer2: {timer2.get_duration()}')
timer2.start()
print('Timer2 started')
time.sleep(1)
print(f'Timer1: {timer.get_duration()}')
print(f'Timer2: {timer2.get_duration()}')
time.sleep(1)
print(f'Timer1: {timer.get_duration()}')
print(f'Timer2: {timer2.get_duration()}')
timer.reset()
timer.start()
print('timer1 reset & restarted')
time.sleep(1)
print(f'Timer1: {timer.get_duration()}')
print(f'Timer2: {timer2.get_duration()}')
time.sleep(1)
print(f'Timer1: {timer.get_duration()}')
print(f'Timer2: {timer2.get_duration()}')
Output:
Timer1: 0:00:01.000916
Timer2: 0:00:01.000916
Timer1: 0:00:02.001026
Timer2: 0:00:02.001026
Timer2 stopped
Timer1: 0:00:03.001449
Timer2: 0:00:02.001026
Timer2 started
Timer1: 0:00:04.001503
Timer2: 0:00:03.001080
Timer1: 0:00:05.001634
Timer2: 0:00:04.001211
timer1 reset & restarted
Timer1: 0:00:01.000573
Timer2: 0:00:05.001784
Timer1: 0:00:02.001267
Timer2: 0:00:06.002478