Python- Custom speed digital Clock - python

I am writing a program in python, and i need a digital clock with custom speed. right now, i am using a clock which is working at a normal pace and sync with the windows time clock. How to write a digital clock with custom speed?

Here's a somewhat over-engineered working example:
import Tkinter as tk
import functools
class UnconventionalClock(tk.Label):
'''A label that displays a clock that runs at an unconvetional rate
usage: x=UnconventionalClock(root, hours, minutes, seconds, interval)
x.start(); ... ; x.stop()
'interval' determines how fast one second is, expressed as milliseconds.
1000 means the clock runs normally; 2000 means it runs at half speed,
500 means it runs at double speed, etc.
'''
def __init__(self, parent, hours=0, minutes=0, seconds=0, interval=1000):
tk.Label.__init__(self, parent)
self._after_id = None
self.reset(hours, minutes, seconds, interval)
def start(self):
self._schedule()
def stop(self):
if self._after_id is not None:
self.after_cancel(self._after_id)
self._after_id = None
def reset(self, hours=0, minutes=0, seconds=0, interval=1000):
self.interval = interval
self.hours = hours
self.minutes = minutes
self.seconds = seconds
self.configure(text=self._format())
def _tick(self):
self.seconds += 1
if self.seconds >= 60:
self.seconds = 0
self.minutes += 1
if self.minutes >= 60:
self.minutes = 0
self.hours += 1
if self.hours >= 24:
self.hours = 0
self.configure(text=self._format())
self._schedule()
def _format(self):
return "%02d:%02d:%02d" % (self.hours, self.minutes, self.seconds)
def _schedule(self):
self._after_id = self.after(self.interval, self._tick)
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.clock = UnconventionalClock(self, 23, 59, 40, 500)
toolbar = tk.Frame(self)
start = tk.Button(toolbar, text="Start", command=self.clock.start)
stop = tk.Button(toolbar, text="Stop", command=self.clock.stop)
start.pack(side="left")
stop.pack(side="left")
toolbar.pack(side="top", fill="x")
self.clock.pack(side="top", fill="x")
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()

custom_time = custom_time0 + (win_time - win_time0)*alpha
where alpha is the custom factor.

Related

Python tkinter: Countdown Timer not accurate

I made a countdown timer using Tkinter in python, but my only problem is that one second in the timer is a bit longer than a real second.
I used the after() function to, every millisecond, remove one millisecond (0.001 second) from the clock.
I don't know if it's doing that because the code of the clock takes some extra time to execute, if that's the case how could I make a clock with the exact same UI that takes less time to execute?
Here's a Video showing the problem
The program:
from tkinter import *
class root(Tk):
def __init__(self):
super(root, self).__init__()
self.title("Timer")
self.buttonplay = Button(self, text = "Play", fg= 'green', command = self.play)
self.buttonplay.pack()
self.buttonpause = Button(self, text = "Pause", fg = "red", command=self.pause)
self.buttonpause.pack()
self.createTimers()
def play(self):
self.timeit=True
self.timer1.configure(bg='#1C953D')
self.doTimer()
def pause(self):
self.timeit=False
self.timer1.configure(bg='#454545')
def reset(self):
self.timer1.destroy()
self.createTimers()
def createTimers(self):
self.minute = 0
self.second = 5
self.ms = 0
self.total = self.second + self.minute *60 + self.ms*0.001
self.time1 = StringVar()
self.time1.set(str(self.minute).rjust(2, '0') + ':' + str(self.second).rjust(2, '0') +'.'+ str(self.ms).rjust(3, '0'))
self.timer1 = Label(self, textvariable=self.time1, bg='#454545', fg='white', font ="Gadugi 40 bold")
self.timer1.pack()
self.timer1.configure(bg='#454545')
def doTimer(self):
self.time = self.second + self.minute *60 + self.ms*0.001
if self.time !=0: #Checks if the timer ended
if self.timeit:
self.timer1.configure(bg='#1C953D')
self.ms = self.ms -1
if self.ms <0:
self.second = self.second -1
self.ms = 999
if self.second == -1:
self.minute = self.minute -1
self.second = 59
self.time1.set(str(self.minute).rjust(2, '0') + ':' + str(self.second).rjust(2, '0') +'.'+ str(self.ms).rjust(3, '0'))
if self.timeit:
self.after(1, self.doTimer)
else:
self.ended = 1
self.timer1.configure(bg='#FF0000')
self.after(3000, self.reset)
root = root()
root.mainloop()
I don't know if it's doing that because the code of the clock takes some extra time to execute
In this case you are right, the tempo of your timer is dependent on the runtime of your code. So making this script require more resources from your computer would also slow down the timer, and vice versa.
"Don't reinvent the wheel." - Programming proverb.
Use the time module to get a more accurate time in your timer. More specifically time.time() and format it to make it readable.
Edit:
Here's an example to show your problem.
import time
time_zone = 0 # UTC
while True:
current_time = time.time()
ms = (current_time * 1000) % 1000
s = (current_time % 60)
m = (current_time % (60*60))//60
h = (current_time % (60*60*24))//(60*60)
print(f"h:{int(h+time_zone)} - m:{int(m)} - s:{int(s)} - ms:{int(ms)}")
time.sleep(1)
In theory this script should print the time exactly every second. However a couple of milliseconds are added due to the code runtime.
Removing time.sleep(1) should get you pretty close to a live clock.
So if you want something close to a timer or stopwatch, you get a timestamp for the current epoch time (time.time()) update your timer about every x-amount of ticks per second to get the effect of counting ms, when the button is pressed again you get a new timestamp, and then you compare it to the first timestamp and subtract to get the exact time between each button press.
I've made a template for you to tinker a bit around with:
import tkinter as tk
import time
time_zone = 0 # UTC
# Convert epoch time to readable format
def readable_epoch(time_epoch):
ms = (time_epoch * 1000) % 1000
s = (time_epoch % 60)
m = (time_epoch % (60*60))//60
h = (time_epoch % (60*60*24))//(60*60)
return (h, m, s, ms)
class App(tk.Tk):
def __init__(self):
super().__init__()
self.time1 = 0
self.time2 = 0
self.geometry("300x200")
self.button1 = tk.Button(
self, text="Click me first", command=self.button1, bg="teal")
self.button1.pack(ipadx=10, ipady=10, expand=True, side=tk.LEFT)
self.button2 = tk.Button(
self, text="Click me second", command=self.button2, bg="pink")
self.button2.pack(ipadx=10, ipady=10, expand=True, side=tk.LEFT)
# Get current time
def button1(self):
self.current_time = time.time()
h, m, s, ms = readable_epoch(self.current_time)
print(f"h:{int(h+time_zone)}, m:{int(m)}, s:{int(s)}, ms:{int(ms)}")
# Elapsed time since button 1 was pressed:
def button2(self):
new_time = time.time()
new_time = new_time - self.current_time
h, m, s, ms = readable_epoch(new_time)
print(f"h:{int(h+time_zone)}, m:{int(m)}, s:{int(s)}, ms:{int(ms)}")
if __name__ == "__main__":
app = App()
app.mainloop()

Timer that prints elapsed time and resets with a button click

I have a GUI where the user can click a button named "next set" that allows them to move onto the next task. I wanted to add a timer that starts as soon as they start the application and run the timer until they press the button "next set". When clicked, I want the time elapsed to print and the timer to restart until they press "next set" button again. I would like the timer to start automatically when the code runs. Currently, the "next set" button has two actions, one is to retrieve the next set of images and the other action I am trying to include is to reset the timer and print time elapsed. I also only included part of the code that felt relevant because it is long.
import time
import tkinter as tk
import csv
from pathlib import Path
import PIL.Image
import PIL.ImageDraw
import PIL.ImageTk
MAX_HEIGHT = 500
IMAGES_PATH = Path("Images")
CSV_LABELS_KEY = "New Labels"
CSV_FILE_NAME_KEY = "FolderNum_SeriesNum"
CSV_BOUNDING_BOX_KEY = "correct_flip_bbox"
counter = 0
timer_id = None
class App(tk.Frame):
def __init__(self, master=None):
super().__init__(master) # python3 style
self.config_paths = ["config 1.yaml", "config 2.yaml", "config 3.yaml"]
self.config_index = 0
self.clickStatus = tk.StringVar()
self.loadedImages = dict()
self.loadedBoxes = dict() # this dictionary will keep track of all the boxes drawn on the images
self.master.title('Slideshow')
frame = tk.Frame(self)
tk.Button(frame, text=" Next set ", command=lambda:[self.get_next_image_set(), self.reset()]).pack(side=tk.RIGHT)
tk.Button(frame, text=" Exit ", command=self.destroy).pack(side=tk.RIGHT)
frame.pack(side=tk.TOP, fill=tk.BOTH)
self.canvas = tk.Canvas(self)
self.canvas.pack()
self._load_dataset()
self.reset()
self.start_timer = None
t = time()
t.start()
def start_timer(self, evt=None):
if self._start_timer is not None:
self._start_timer = time.perf_counter()
# global counter
# counter += 1
# label.config(text=str(counter))
# label.after(1000, count)
def reset(self):
if self._start_timer is None:
elapsed_time = time.perf_counter() - self._start_timer
self._start_timer = None
print('Time elapsed (hh:mm:ss.ms) {}'.format(elapsed_time))
def _load_dataset(self):
try:
config_path = self.config_paths[self.config_index]
self.config_index += 1
except IndexError:
return
image_data = loadData(config_path)
# drawing the image on the label
self.image_data = image_data
self.currentIndex = 0
# start from 0th image
self._load_image()
def _load_image(self):
imgName = self.image_data[self.currentIndex]['image_file']
if imgName not in self.loadedImages:
self.im = PIL.Image.open(self.image_data[self.currentIndex]['image_file'])
ratio = MAX_HEIGHT / self.im.height
# ratio divided by existing height -> to get constant amount
height, width = int(self.im.height * ratio), int(self.im.width * ratio)
# calculate the new h and w and then resize next
self.canvas.config(width=width, height=height)
self.im = self.im.resize((width, height))
if self.im.mode == "1":
self.img = PIL.ImageTk.BitmapImage(self.im, foreground="white")
else:
self.img = PIL.ImageTk.PhotoImage(self.im)
imgData = self.loadedImages.setdefault(self.image_data[self.currentIndex]['image_file'], dict())
imgData['image'] = self.img
imgData['shapes'] = self.image_data[self.currentIndex]['shapes']
# for next and previous so it loads the same image adn don't do calculations again
self.img = self.loadedImages[self.image_data[self.currentIndex]['image_file']]['image']
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.img)
self.show_drag_box()
def loadData(fname):
with open(fname, mode='r') as f:
return yaml.load(f.read(), Loader=yaml.SafeLoader)
if __name__ == "__main__":
data = loadData('config 1.yaml')
app = App(data)
app.pack() # goes here
app.mainloop()
I have used datetime instead of time, as subtracting two datetime objects will give an output with hours and minutes included, whereas subtracting two time objects only gives seconds. However, both will work, you may just need to do more reformatting using time.
Read the current time when the application starts and store it. Each time you press the button, subtract your stored time from the current time which gives you your time elapsed. Then simply store your new current time until the next button press. The code below demonstrates this.
import tkinter as tk
import datetime as dt
class TimeButton(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
# Start timer
self.current_time = dt.datetime.today()
# Button
self.next_set = tk.Button(self, text='Next Set', command = self.clicked)
self.next_set.pack()
def clicked(self):
now = dt.datetime.today()
time_elapsed = now - self.current_time
print(time_elapsed)
self.current_time = now
if __name__ == "__main__":
window = tk.Tk()
button = TimeButton(window)
button.pack()
window.mainloop()

Tkinter after() getting faster whenever my timer object is reset

I'm trying to write a timer with tkinter.
And here's my class program of timer
class Timer:
def __init__(self, root, cv):
self.sec = 0
self.stop = 0
self.label = tk.Label(root, width = 15, height = 1, text = "time:"+"{}".format(str(self.sec))+"s", bg= "GhostWhite")
self.label.config(font = ("helvetica", 8))
cv.create_window(300, 14, window = self.label)
self.label.after(1000, self.refresh)
def refresh(self):
self.sec += 1
self.label.configure(text="time:"+"{}".format(str(self.sec))+"s")
if self.stop == 0:
self.label.after(1000, self.refresh)
def halt(self):
self.stop = 1
def reset(self):
self.sec = 0
self.stop = 0
self.label.after(1000, self.refresh)
The problem is, the timer runs faster each time the reset method is called.
I try to figure out the problem but I just don't see anything wrong.
Any idea?

Error when trying to display a timer into button text

When i run this code everything seems to work fine, then when i click my blue buff button where I'm expecting my time countdown to start being displayed the button text instead changes to function my_time at 0x000000... etc. some memory location im struggling to find a solution. If its unclear what im trying to do is have the text Blue Buff change to the to a countdown.
from Tkinter import *
import os
import time
class Application(Frame):
#staticmethod
def my_time(self): # creates a timer starting at 5 min , counts down to 0 then repeats
min = 4
sec = 59
while sec <=60:
os.system('cls')
print min, "Minutes", sec, "Seconds"
time.sleep(1)
sec -= 1
if sec == 0:
min -= 1
sec = 59
elif min == 0:
min = 4
def my_time2(self): # tries to display my_time method into the button font, doesnt seem to work.
self.Button1["text"] = str(self.my_time)
def createButtons(self): # creats a button
self.Button1 = Button(self)
self.Button1["text"] = "Blue Buff"
self.Button1["fg"] = "Blue"
self.Button1["command"] = self.my_time2 # suppose to implement countdown in button text when click.
# currently problematic?
self.Button1.pack({"side": "left"})
def __init__(self, master=None):
Frame.__init__(self, master) # initializes window
self.pack()
self.createButtons()
root = Tk()
app = Application(master=root)
app.mainloop()
This should do what you need:
from Tkinter import *
import os
import time
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master) # initializes window
self.Button1 = Button(self, text="Blue Buff", command=self.my_time)
self.Button1.pack()
self.pack()
def my_time(self): # creates a timer starting at 5 min , counts down to 0 then repeats
min = 4
sec = 59
while sec <=60:
os.system('cls')
print min, "Minutes", sec, "Seconds"
self.Button1.configure(text="{}:{}".format(min,sec))
self.Button1.update()
time.sleep(1)
sec -= 1
if sec == 0:
min -= 1
sec = 59
elif min == 0:
min = 1
root = Tk()
app = Application(master=root)
app.mainloop()#
Using your own code just set command to my_time and update the Button1:
class Application(Frame):
def my_time(self): # creates a timer starting at 5 min , counts down to 0 then repeats
min = 4
sec = 59
while sec <=60:
self.Button1.configure(text="{}:{}".format(min,sec))
self.Button1.update()
os.system('cls')
print min, "Minutes", sec, "Seconds"
time.sleep(1)
sec -= 1
if sec == 0:
min -= 1
sec = 59
elif min == 0:
min = 4
def my_time2(self): # tries to display my_time method into the button font, doesnt seem to work.
self.Button1["text"] = str(self.my_time)
def createButtons(self): # creats a button
self.Button1 = Button(self)
self.Button1["text"] = "Blue Buff"
self.Button1["fg"] = "Blue"
self.Button1["command"] = self.my_time # suppose to implement countdown in button text when click.
# currently problematic?
self.Button1.pack({"side": "left"})
def __init__(self, master=None):
Frame.__init__(self, master) # initializes window
self.pack()
self.createButtons()
It looks like you are setting self.Button1["command"] = self.my_time2 in which self.my_time2 is not a an attribute, but rather a function. That would be why the numbers look all wonky, is because its displaying a memory address and not the str(self.my_time) that you would expect. Perhaps you meant to invoke my_time2()
*EDIT 1
changing line 25 to
self.Button1["text"] = str(self.my_time(self))
Also what you have written in my_time will print to stdout, and not actually update the button (which is what I think you want?), but i'll let you figure that out as the designer.

pyHook + Tkinter = crash?

Consider the following example:
from Tkinter import *
import pyHook
class Main:
def __init__(self):
self.root = Tk()
self.root.protocol("WM_DELETE_WINDOW", self.onClose)
self.root.title("Timer - 000")
self.timerView = Text(self.root,
background="#000",
foreground="#0C0",
font=("Arial", 200),
height=1,
width=3)
self.timerView.pack(fill=BOTH, expand=1)
self.timer = 0
self.tick()
self.createMouseHooks()
self.root.mainloop()
def onClose(self):
self.root.destroy()
def createMouseHooks(self):
self.mouseHook = pyHook.HookManager()
self.mouseHook.SubscribeMouseAllButtons(self.mouseClick)
self.mouseHook.HookMouse()
def mouseClick(self, event):
self.timer = 300
return True
def tick(self):
self.timerView.delete(1.0, END)
self.timerView.insert(END, self.threeDigits(self.timer))
self.root.title("Timer - " + str(self.threeDigits(self.timer)))
self.timer = self.timer - 1 if self.timer > 0 else 0
self.root.after(1000, self.tick)
def threeDigits(self, number):
number = str(number)
while len(number) < 3:
number = "0" + number
return number
if __name__ == "__main__":
Main()
This will display a window and asynchronously update a text widget every second. It's simply a timer that will count down, and reset to 300 whenever the user clicks a mouse button.
This does work, but there's a weird bug. When the program is running, and you move the window, the mouse and program will freeze for 3-4 seconds, and then the program stops responding.
If you remove the hook or the asynchronous update, the bug won't happen.
What could be the cause of this problem?
EDIT:
I've been testing in Windows 7 with Python 2.6.

Categories