Tkinter .after() method going faster than it should - python

I been working on a simple Tkinter Gui in which a timer gets involved. The thing is that the timer goes faster than the milliseconds specified in the .after() method. Here is my code:
import tkinter
import time
from tkinter import *
seconds = 604800
FONT = ("Arial", 24)
window = tkinter.Tk()
window.attributes('-topmost', True)
window.attributes('-fullscreen', True)
window.title("Sandbox Crypto")
window.configure(bg='red')
seconds = 604800
def gui():
text = StringVar()
def substract_seconds():
global seconds
seconds -=1
while seconds > 0:
mins, secs = divmod(seconds, 60)
hours, mins = divmod(mins, 60)
days, hours = divmod(hours, 24)
timer = '{:02d}:{:02d}:{:02d}:{:02d}'.format(days,hours,mins, secs)
text.set(timer)
Time_label = Label(window, textvariable=text, bg='red', fg='white', font=FONT)
Time_label.grid()
Time_label.place(x=10, y=300)
Time_label.update()
Time_label.after(1000, substract_seconds)
window.mainloop()
gui()
The strange thing here is that i investigated the .after() method common errors and most of them were related to the method actually going slower than it should. One of my theories is that is an error related to the CPU speed because the speed of the clock varies through the time. What I infer from this is that sometimes it goes faster and then it slows down and continue going faster.

Since you used while loop, there is new scheduled task created to update the seconds in every iteration.
You don't need the while loop at all, below is modified gui():
def gui():
text = StringVar()
Time_label = Label(window, textvariable=text, bg='red', fg='white', font=FONT)
Time_label.place(x=10, y=300)
def countdown(seconds=seconds):
mins, secs = divmod(seconds, 60)
hours, mins = divmod(mins, 60)
days, hours = divmod(hours, 24)
timer = '{:02d}:{:02d}:{:02d}:{:02d}'.format(days,hours,mins, secs)
text.set(timer)
if seconds > 0:
Time_label.after(1000, countdown, seconds-1)
countdown() # start the count down
window.mainloop()

Related

tkinter unkown error with window updating

i wrote a simple program in tkinter to remind me for medicines every fixed interval of time, and when the alarm rings a snooze button should appear which should set the timer to 10 minutes and after that alarm should ring again and if stop is pressed instead of snooze this time it should reset the timer to initially given fixed interval of time.
i don't understand why the snooze button never appears after alarm plays
here's the code :-
import tkinter as tk
import sounddevice as sd
import soundfile as sf
import time
stop = False
def start_alarm():
global interval
interval = int(entry.get()) # get the interval from the user
time_left.config(text=str(interval) + " seconds left")
countdown()
def stop_alarm():
global stop
stop = True
sd.stop()
start_alarm()
def snooze_alarm():
global stop
stop = True
sd.stop()
interval += 600
time_left.config(text=str(interval) + " seconds left")
countdown()
def countdown():
global interval
global stop
if interval > 0 and not stop:
interval -= 1
time_left.config(text=str(interval) + " seconds left")
time_left.after(1000, countdown)
elif not stop:
play_alarm()
def play_alarm():
data, fs = sf.read('alarm.wav', dtype='float32')
sd.play(data, fs)
status = sd.wait()
create_snooze_button()
def create_snooze_button():
global snooze_button
snooze_button = tk.Button(root, text="Snooze", command=snooze_alarm)
snooze_button.pack()
root = tk.Tk()
root.title("Medicine Reminder")
label = tk.Label(root, text="Enter time interval (in seconds):")
label.pack()
entry = tk.Entry(root)
entry.pack()
start_button = tk.Button(root, text="Start", command=start_alarm)
start_button.pack()
stop_button = tk.Button(root, text="Stop", command=stop_alarm)
stop_button.pack()
time_left = tk.Label(root, text="")
time_left.pack()
root.mainloop()
any help is highly appreciated

How to create mutliple child windows from tkinter root window?

I am new to python and learning tkinter apps. I am trying to build an app that can spawn multiple instances of countdown timer and run them separately. I have been able to create a template code and can run one child window successfully but when I open another child window first one pauses and only the last one runs.
MWE
How to make all the child window countdown timers run separately?
Problem: If I click Scripts menu, then the countdown pauses itself.
%%writefile a.py
import time
import tkinter as tk
from tkinter import ttk,messagebox
def countdown(win):
child = tk.Toplevel(win)
child.geometry('400x300')
child.resizable(0, 0)
def _show_current_clock_time(label):
str_clock_time = time.strftime('%H:%M:%S %p')
label.config(text=str_clock_time)
label.after(1000, lambda: _show_current_clock_time(label))
def _countdown(child,v_hrs,v_mins,v_secs,int_secs):
if int_secs is not None:
total_seconds = int_secs
else:
total_seconds = int(v_hrs.get()) * 3600 + int(v_mins.get()) * 60 + int(v_secs.get())
while total_seconds > -1:
minute, second = (total_seconds // 60, total_seconds % 60)
hour = 0
if minute > 60:
hour, minute = (minute // 60, minute % 60)
v_secs.set(second);v_mins.set(minute);v_hrs.set(hour)
child.update();time.sleep(1)
if (total_seconds == 0):
messagebox.showinfo('Times Up!')
v_secs.set('00');v_mins.set('00');v_hrs.set('00')
total_seconds -= 1
tk.Label(child,text='Countdown Clock and Timer').pack()
tk.Label(child, font='arial 15 bold', text='current time :').place(x=40, y=70)
# label: current time body
l_curr_time = tk.Label(child)
l_curr_time.place(x=190, y=70)
_show_current_clock_time(l_curr_time)
# variables: secs mins hrs
v_secs = tk.StringVar()
e_secs = tk.Entry(child, textvariable=v_secs, width=2, font='arial 12')
e_secs.place(x=250, y=155)
v_secs.set('00')
v_mins = tk.StringVar()
e_mins = tk.Entry(child, textvariable=v_mins, width=2, font='arial 12')
e_mins.place(x=225, y=155)
v_mins.set('00')
v_hrs = tk.StringVar()
e_hrs = tk.Entry(child, textvariable=v_hrs, width=2, font='arial 12')
e_hrs.place(x=200, y=155)
v_hrs.set('00')
# label: set the time
tk.Label(child, font='arial 15 bold', text='set the time',bg='papaya whip').place(x=40, y=150)
# button: start
x,w = 0,40
tk.Button(child,text='Run',bd='1',
command= lambda x=[child,v_hrs,v_mins,v_secs,None]: _countdown(x[0],x[1],x[2],x[3],x[4])).place(x=280, y=150)
for i,minn in enumerate([1,2,3,4]):
tk.Button(child,text=str(minn)+'m',
command= lambda x=[child,v_hrs,v_mins,v_secs,minn*60]: _countdown(
x[0],x[1],x[2],x[3],x[4]),).place(x=x+w*i, y=200)
win = tk.Tk()
menubar = tk.Menu(win)
menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Scripts", menu=menu)
menu.add_command(label='Countdown',command=lambda : countdown(win))
menu.add_command(label='Countdown 2',command=lambda : countdown(win))
menu.add_command(label='Countdown 3',command=lambda : countdown(win))
win.config(menu=menubar)
win.mainloop()

Can't stop timer in python (after and after_cancel)

I am trying to create a timer in a tkinter GUI that initiates on a start button, and stops on a stop button. I am using .after to loop the timer, but I can't correctly integrate .after_cancel. Any advice would be greatly appreciated.
#import python modules
import time
import math
from tkinter import *
#Create a root window to work in
root = Tk()
root.title("CNT Synthesis Controller") #title of file
#Create global variables to be stored
global var_status
global current_time
global start_time
current_time = 0
#Create Labels
myLabel_Status_T = Label(root, text="Total time elapsed (hrs:min:sec):")
myLabel_Timer = Label(root, text="00:00:00")
#Locate labels
myLabel_Status_T.grid(row=0, column=0)
myLabel_Timer.grid(row=1, column=0)
#Start button function
def Click_Start():
#disable Start Button to prevent re-start
myButton_Start.config(state=DISABLED)
#initiate time = 0 and start timer
start_time = time.time()
global Timer_continue
Timer_continue = True
#Timer function
def Timer():
#determine the amount of time passed since start_time measured
current_time = int(time.time()-start_time)
hour = math.floor(current_time/3600)
minute = math.floor((current_time/60)-(hour*60))
second = math.floor(current_time-(hour*3600)-(minute*60))
#shows time as 00:00:00, i.e. adding in the zeroes where needed
if hour<10:
hour=str("0"+str(hour))
else:
hour=str(hour)
if minute<10:
minute=str("0"+str(minute))
else:
minute=str(minute)
if second<10:
second=str("0"+str(second))
else:
second=str(second)
#print the time to the label myLabel_Timer
myLabel_Timer.config(text= hour + ":" + minute + ":" + second)
#after 1000 ms repeat the Timer function
#while (myButton_Stop['state'] != tk.DISABLED):
#Timer_Object=myLabel_Timer.after(1000,Timer)
if Timer_continue == True:
root.after(1000,Timer)
if Timer_continue == False:
root.after_cancel(Timer)
Timer()
#Stop button function
def Click_Stop():
Timer_continue = False
#disable Stop Button to prevent re-use until program is reset
myButton_Stop.config(state=DISABLED)
#Create Buttons
myButton_Start = Button(root,text="Start CNT Synthesis", padx=40, pady=20, fg="white", bg="green", command=Click_Start)
myButton_Stop = Button(root,text="Stop CNT Synthesis", padx=40, pady=20, fg="white", bg="red", command=Click_Stop)
#Locate buttons
myButton_Start.grid(row=2, column=0)
myButton_Stop.grid(row=2, column=1)
root.mainloop()
####---------------------------------------------------------------------Required added text to make the question 'long enough'--------------------------------------------------------------------------------------------##########
You need to save the ID returned by .after() and use it in .after_cancel() to stop the timer inside Click_Stop() function:
#import python modules
import time
from tkinter import *
#Create a root window to work in
root = Tk()
root.title("CNT Synthesis Controller") #title of file
#Create Labels
myLabel_Status_T = Label(root, text="Total time elapsed (hrs:min:sec):")
myLabel_Timer = Label(root, text="00:00:00")
#Locate labels
myLabel_Status_T.grid(row=0, column=0)
myLabel_Timer.grid(row=1, column=0)
Timer_id = None # used to store the ID returned by .after()
#Start button function
def Click_Start():
#disable Start Button to prevent re-start
myButton_Start.config(state=DISABLED)
#enable Stop Button
myButton_Stop.config(state=NORMAL)
#save the start timer
start_time = time.time()
#Timer function
def Timer():
global Timer_id
#determine the amount of time passed since start_time measured
elapsed = int(time.time()-start_time)
minutes, seconds = divmod(elapsed, 60)
hours, minutes = divmod(minutes, 60)
myLabel_Timer.config(text=f"{hours:02}:{minutes:02}:{seconds:02}")
#after 1000 ms repeat the Timer function
Timer_id = root.after(1000,Timer) # save the after ID
Timer()
#Stop button function
def Click_Stop():
if Timer_id:
root.after_cancel(Timer_id) # cancel the scheduled task
#disable Stop Button to prevent re-use until program is reset
myButton_Stop.config(state=DISABLED)
#enable Start Button
myButton_Start.config(state=NORMAL)
#Create Buttons
myButton_Start = Button(root,text="Start CNT Synthesis", padx=40, pady=20,
fg="white", bg="green", command=Click_Start)
myButton_Stop = Button(root,text="Stop CNT Synthesis", padx=40, pady=20,
fg="white", bg="red", command=Click_Stop, state=DISABLED) # disable stop button initially
#Locate buttons
myButton_Start.grid(row=2, column=0)
myButton_Stop.grid(row=2, column=1)
root.mainloop()
Miss the global statement:
def Click_Stop():
global Timer_continue
Timer_continue = False

python GUI countdown timer (don't use classes)

i have just one problem in my source code, lbl_time will not change, all thing running well except this.
I could only use function in my program, if someone can help me in this functional program, please do it.
import tkinter as tk
from datetime import timedelta
import winsound
set time is a function that for counting down time, i use timedelta for building a time object to simple operation
def main():
def set_time(hours:int=0, minutes:int=0, seconds:int=0):
end = timedelta(hours=hours, minutes=minutes, seconds=seconds)
one_second = timedelta(seconds=1)
result = end - one_second
new_time = seconds_to_hms(result.seconds)
if result.seconds is 0:
while True:
try:
winsound.PlaySound("Ringtones\\1.cookie clock.wav", winsound.SND_FILENAME)
except KeyboardInterrupt:
break
else:
hours, minutes, seconds = new_time.get('hours'), new_time.get('minutes'), new_time.get('seconds')
time.set(str(hours)+':'+str(minutes)+':'+str(seconds))
root.update()
root.after(1000, lambda : set_time(hours, minutes, seconds))
def seconds_to_hms(seconds:int) -> dict:
hours, minutes, seconds = 0, 0, seconds
if seconds >= 3600:
hours = seconds//3600
seconds = seconds - hours*3600
if seconds >= 60:
minutes = seconds//60
seconds = seconds - minutes*60
result = {'hours': hours, 'minutes': minutes, 'seconds': seconds}
return result
def quit(*args):
root.destroy()
root = tk.Tk()
root.title(string='Timer')
time = tk.StringVar()
root.configure(background='black')
logo = tk.PhotoImage(file='Logo.png')
lbl_logo = tk.Label(root, image=logo, bg='black').pack(side='right')
lbl_timer = tk.Label(root, padx=10, text="Timer", fg='white', bg='black', font='Times 24', anchor='center').pack()
lbl_time = tk.Label(root, text=time, font="Times 38", fg='white', bg='black').pack()
btn_start = tk.Button(root, text='start', bg='gray', fg='black', command=lambda : set_time()).pack()
root.bind('x', quit)
root.after(1000, lambda : set_time(0,0, 3))
root.mainloop()
if __name__ == '__main__':
main()
Took some time to understand your question, but I might have solved your problem.
Fundamentally you ought to have used textvariable rather than text as an argument in your lbl_time declaration.
If your while loop is executed, it might result in an infinite loop, you might want to have an increment if the code branches there.
Do check out the following, you might want to uncomment some of the lines, hopefully it does solve your problem:
import tkinter as tk
from datetime import timedelta
import winsound
def main():
def set_time(hours:int=0, minutes:int=0, seconds:int=0):
end = timedelta(hours=hours, minutes=minutes, seconds=seconds)
one_second = timedelta(seconds=1)
result = end - one_second
new_time = seconds_to_hms(result.seconds)
if result.seconds is 0:
while True:
try:
winsound.PlaySound("Ringtones\\1.cookie clock.wav", winsound.SND_FILENAME)
except KeyboardInterrupt:
break
else:
hours, minutes, seconds = new_time.get('hours'), new_time.get('minutes'), new_time.get('seconds')
time.set(str(hours)+':'+str(minutes)+':'+str(seconds))
root.update()
root.after(1000, lambda : set_time(hours, minutes, seconds))
def seconds_to_hms(seconds:int) -> dict:
hours, minutes, seconds = 0, 0, seconds
if seconds >= 3600:
hours = seconds//3600
seconds = seconds - hours*3600
if seconds >= 60:
minutes = seconds//60
seconds = seconds - minutes*60
result = {'hours': hours, 'minutes': minutes, 'seconds': seconds}
return result
def quit(*args):
root.destroy()
root = tk.Tk()
root.title(string='Timer')
time = tk.StringVar()
root.configure(background='black')
# logo = tk.PhotoImage(file='Logo.png')
# lbl_logo = tk.Label(root, image=logo, bg='black').pack(side='right')
lbl_timer = tk.Label(root, padx=10, text="Timer", fg='white', bg='black', font='Times 24', anchor='center').pack()
lbl_time = tk.Label(root, textvariable=time, font="Times 38", fg='white', bg='black').pack() #changed text to textvariable
btn_start = tk.Button(root, text='start', bg='gray', fg='black', command=lambda :set_time(0,0,3700)).pack()
root.bind('x', quit)
#root.after(1000, lambda : set_time(0,0, 3))
root.mainloop()
if __name__ == '__main__':
main()

How to make a timer count down to a time you assign for it python

I am trying to make a program which counts down to a specific time. In this program I have assigned the time when I want it to say click me but I am having trouble figuring out how to make a timer which counts down to that time. This is the code I currently have:
import time
from tkinter import *
from datetime import datetime
from threading import Timer
tk = Tk()
canvas = Canvas(tk, width=400, height=400)
canvas.pack()
x = datetime.today()
y = x.replace(day=x.day, hour=1, minute=30, second=0, microsecond=0)
delta_t = y-x
secs = delta_t.seconds+1
def hello_world():
label = Label(tk, text="CLICK NOW", font=('Times', 45), fg='blue')
label.place(relx=0.5, rely=0.5, anchor=CENTER)
t = Timer(secs, hello_world)
t.start()
tk.mainloop()
If anyone has any suggestions to have the timer countdown to the specified time it would be greatly appreciated. Thanks in advance for any help
Shouldn't you do?
secs = delta_t.total_seconds() + 1
Instead of
secs = delta_t.seconds + 1
Here's a very simple timer made using tkinter that you can incorporate into your code: just use it as .after()
from tkinter import *
root = Tk()
msg = Label(root, text = str(60))
msg.pack()
def timer():
msg['text']=str(int(msg['text'])-1)
root.after(60, timer) # continue the timer
root.after(60, timer) # 60 ms = 1 second
root.mainloop()
This is the most basic concept of making a timer using tkinter. And it will be very useful.

Categories

Resources