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

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

Related

why is the display not updating

i want to do a countdown but the changes are not showing until the final one. Example:
def countdown():
# setup of window and other labels etc
timer = calculated time # just placeholder
countdown_time = Message(countdownWindow, text=timer, font=("Courier",16,"bold"), width=100)
time.sleep(1)
while timer != "00:00:00":
timer = calculate time # this time will be a updated current time
root.after(1000, countdown_time.configure(text=timer)
root.after(1000, countdown)
the problem here is that it only opens the window once the final change has been complete, but I want it to update the label every time it is changed
according to your example:
from tkinter import Tk, Button, Label
import time
def countdown():
time_count = 0
time.sleep(1)
while time_count != 10:
time_count += 1
root.after(1000, lbl.configure(text=str(time_count)))
root.update()
root.after(1000, countdown) # invokes an endless loop
root = Tk()
btn = Button(root, text="countdown", command=countdown)
btn.pack()
lbl = Label(root, text="")
lbl.pack()
root.mainloop()

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

Tkinter Entry Widget: How to constantly check for activity and delete the field if nothing is typed in a period of time

Trying to write a little program where you type in a Tkinter Entry widget and if you don't type for 5 seconds it deletes everything. The best I can do is have it delete everything on the first key pressed after the five seconds elapses, but I can't figure out how to get it to do it without that extra key press.
import time
from tkinter import *
def click(key):
global click_before_last_click, last_click
# print(key.char)
click_before_last_click = last_click
last_click = time.time()
# print(click_before_last_click)
# print(last_click)
delete_shit()
def disappearing_text_start():
global click_before_last_click, last_click
click_before_last_click = time.time()
last_click = time.time()
entry.delete(1.0, END)
entry.bind("<Key>", click)
def disappearing_text_end():
text_file = open("result.txt", "w")
text_file.write(entry.get(1.0, END))
text_file.close()
entry.delete(1.0, END)
def delete_shit():
if last_click > click_before_last_click + 5:
print("TOO LONG")
entry.delete(1.0, END)
if __name__ == "__main__":
click_before_last_click = time.time()
last_click = time.time()
window = Tk()
window.title("Disappearing Text")
window.config(padx=50, pady=20, bg="#D3D3D3")
title_label = Label(text="Disappearing Text App", fg="black", bg="#D3D3D3", font=("Courier", 24))
title_label.grid(column=1, row=0, columnspan=2)
label = Label(text="Click start to begin, and end to save your text. "
"If you stop typing for 5 seconds, you lose everything.",
bg="#D3D3D3", font=("Courier", 14))
label.grid(column=1, row=1, columnspan=2)
entry = Text(width=100, height=30)
entry.grid(column=1, columnspan=2, row=3)
start_button = Button(text="Start", command=disappearing_text_start)
start_button.grid(column=1, row=4, pady=20)
end_button = Button(text="Save", command=disappearing_text_end)
end_button.grid(column=2, row=4, pady=20)
window.mainloop()
You can use after to delete the characters after the time interval. Each time the user presses a key, delete the old scheduled function and then reschedule it.
Also, FWIW, you've used an index of 1.0 which is invalid. Tkinter will accept it, but an index is a string rather than a floating point number.
Let's start by writing a function that will schedule the text to be deleted in 5 seconds. It will also cancel any pending job, effectively resetting the timer to zero. It needs to accept an event parameter since it will be called from a key binding.
after_id = None
def schedule_delete(event=None):
global after_id
if after_id:
window.after_cancel(after_id)
after_id = window.after(5000, delete_shit)
Next, arrange for this to be called when the user clicks the "start" button. disappearing_text_start might look something like this:
def disappearing_text_start():
schedule_delete()
entry.delete("1.0", END)
You can then call this bind command once in the main body of of your program to have reschedule_delete called on every keypress:
entry.bind("<Any-KeyPress>", reschedule_delete)
Finally, we need to cancel any pending job when the user clicks the "stop" button:
def disappearing_text_end():
global after_id
if after_id:
window.after_cancel(after_id)
after_id = None
... the rest of your code here ...
Here's a complete working example:
from tkinter import *
def click(key):
schedule_delete()
def disappearing_text_start():
schedule_delete()
entry.delete("1.0", END)
def schedule_delete(event=None):
global after_id
if after_id:
window.after_cancel(after_id)
after_id = window.after(5000, delete_shit)
def disappearing_text_end():
global after_id
if after_id:
window.after_cancel(after_id)
after_id = None
text_file = open("result.txt", "w")
text_file.write(entry.get(1.0, END))
text_file.close()
entry.delete("1.0", END)
def delete_shit():
entry.delete("1.0", END)
if __name__ == "__main__":
# this is used to keep track of the scheduled function call
after_id = None
window = Tk()
window.title("Disappearing Text")
window.config(padx=50, pady=20, bg="#D3D3D3")
title_label = Label(text="Disappearing Text App", fg="black", bg="#D3D3D3", font=("Courier", 24))
title_label.grid(column=1, row=0, columnspan=2)
label = Label(text="Click start to begin, and end to save your text. "
"If you stop typing for 5 seconds, you lose everything.",
bg="#D3D3D3", font=("Courier", 14))
label.grid(column=1, row=1, columnspan=2)
entry = Text(width=100, height=30)
entry.grid(column=1, columnspan=2, row=3)
start_button = Button(text="Start", command=disappearing_text_start)
start_button.grid(column=1, row=4, pady=20)
end_button = Button(text="Save", command=disappearing_text_end)
end_button.grid(column=2, row=4, pady=20)
entry.bind("<Any-KeyPress>", schedule_delete)
window.mainloop()

Call a fonction from message box Tkinter

I want to call the fonction (submit) after clicking on the message box. With this code I have to reclick on button everytime after the messagebox but would appreciate if the timer launch automatically after clicking on the message box.
If anyone have a clue I will appreciate.
The following code :
import time
from tkinter import *
root = Tk()
root.resizable(width=False, height=False)
root.geometry("300x250")
root['background']='#39E5F9'
root.title("Time To Drink Water")
minute = StringVar()
second = StringVar()
minute.set("45")
second.set("00")
minuteEntry = Entry(root, width=3, font=("Arial", 35, ""),
textvariable=minute,justify='center')
minuteEntry.place(x=50, y=60)
secondEntry = Entry(root, width=3, font=("Arial", 35, ""),
textvariable=second,justify='center')
secondEntry.place(x=170, y=60)
def submit():
# stored in here : 2700 = 45 mins
temp = 2700
while temp > -1:
# divmod(firstvalue = temp//60, secondvalue = temp%60)
mins, secs = divmod(temp, 60)
if mins > 60:
hours, mins = divmod(mins, 60)
minute.set("{0:2d}".format(mins))
second.set("{0:2d}".format(secs))
root.update()
time.sleep(1)
if (temp == 0):
messagebox.showinfo("Time Countdown", "Time To Drink !")
temp -= 1
def go():
btn = Button(root, text='Goodbye dehydration!', bd='6',
command=submit)
btn.place(x=90, y=160)
go()
root.mainloop()
messagebox is waiting for your click so you can run submit() after messagebox and it will run it after clicking in messagebox
But you shouldn't use while and sleep because it may freeze GUI (in any framework, and in any language). You can use root.after(1000, function) to run function again after 1000ms (1s) and it will work as sleep and while together.
import time
import tkinter as tk # PEP8: `import *` is not preferred
from tkinter import messagebox
# --- functions --- # PEP8: all functions before main code
def submit():
update_counter(2700)
def update_counter(temp):
if temp > -1: # `if` instead of `while` because `after` will work as loop
# divmod(firstvalue = temp//60, secondvalue = temp%60)
mins, secs = divmod(temp, 60)
if mins > 60:
hours, mins = divmod(mins, 60)
minute.set("{:02d}".format(mins)) # use `:02` to get `09` instead of ` 9` (with space)
second.set("{:02d}".format(secs))
temp -= 1
root.after(1000, update_counter, temp) # run again after 1000ms
else:
messagebox.showinfo("Time Countdown", "Time To Drink !")
root.after(0, update_counter, 2700) # run again after 0ms
#update_counter(2700) # run again
# --- main --- # PEP8: `lower_case_names` for variables
running = False
temp = 2700
root = tk.Tk()
minute = tk.StringVar(root)
second = tk.StringVar(root)
minute.set("45")
second.set("00")
minute_entry = tk.Entry(root, width=3, textvariable=minute, font=("Arial", 35, ""), justify='center')
minute_entry.grid(row=0, column=0)
second_entry = tk.Entry(root, width=3, textvariable=second, font=("Arial", 35, ""), justify='center')
second_entry.grid(row=0, column=1)
btn = tk.Button(root, text='Goodbye dehydration!', command=submit)
btn.grid(row=1, column=0, columnspan=2)
root.mainloop()
PEP 8 -- Style Guide for Python Code
There is other problem. You can click button two times and it will run two update_counter() at the same time. It may need to disable button, or you would have to use boolean variable - ie. running = False - to control if it has to run update_counter() or not.

How to fix timer increasing countdown speed with each button press

I already read a thread that helped my problem with turning the button[state] to 'normal' and 'disabled'.
I would really like to know and learn if there is a conditional/boolean way to do this.
How, or can you start a timer with a button and not have the timer speed up if a user clicks the start button again?
I tried several times to implement a conditional, checking if the button had been pressed, if the time was less than the original variable amount, etc.
But each time the timer would countdown faster and faster with each click. I feel like there is an issue on my part in understanding the fundamentals of how conditionals/booleans work.
from tkinter import *
root = Tk()
hold_time = 120
timer = False
def start_timer():
global timer
timer = True
hold_timer_start()
start_button['state'] = 'disabled'
def hold_timer_start():
global hold_time
global timer
if timer == True:
hold_time -= 1
hold_timer_label['text'] = hold_time
hold_timer_label.after(1000, hold_timer_start)
if hold_time == 0:
timer = False
def hold_timer_stop():
global hold_time
global timer
hold_time = 120
hold_timer_label['text'] = hold_time
timer = False
start_button['state'] = 'normal'
timer_frame = Frame(root, bg='gray25')
timer_frame.grid(row=0, column=0)
hold_timer_label = Label(timer_frame, text='Hold Time', bg='skyblue')
hold_timer_label.grid(row=0, column=1, sticky='ew', columnspan=2, pady=2)
hold_timer_label = Label(timer_frame, text=hold_time, bg='white')
hold_timer_label.grid(row=1, column=1, sticky='ew', columnspan=2, pady=2)
empty_label = Label(timer_frame, bg='gray25')
empty_label.grid(row=2, column=0)
start_button = Button(timer_frame, text='Start', bg='green', fg='black', command=start_timer)
start_button.grid(row=2, column=1)
stop_button = Button(timer_frame, text='Stop', bg='red', fg='black',command=hold_timer_stop)
stop_button.grid(row=2, column=2)
empty_label = Label(timer_frame, bg='gray25')
empty_label.grid(row=2, column=3)
root.mainloop()
I expect the timer to start counting down once the button is pressed but then do nothing if pressed again while the timer is counting down.
Unless the timer has hit zero or the stop button has been pressed.
You already have a timer flag set. All you need to do is to check it.
def start_timer():
global timer
if not timer:
timer = True
hold_timer_start()
#start_button['state'] = 'disabled'

Categories

Resources