I am attempting to make a clock using python's Tkinter. It works but not in the way I intended. Once the user enters the amount of time that needs to be counted down from, the action is performed but the actual countdown isn't being shown. I would like for the user to have the ability to watch the numbers go all the way until 0. Any help is greatly appreciated.
import time
from tkinter import *
root = Tk()
def countdown(t):
ts = int(t)
while ts > 0:
timer = Label(timerFrame, text = ts)
ts-=1
timer.pack()
time.sleep(1)
if ts ==0:
timer.destroy()
completeTimer = Label(timerFrame, text="Time is complete")
completeTimer.pack()
timerFrame = LabelFrame(root, padx=50, pady=50, bd=0)
timerFrameText = Label(timerFrame,
text="Enter time in seconds for countdown",
font=("Arial", 20, "bold")
)
countdownBox= Entry(timerFrame, bd=3)
submitCountdown = Button(timerFrame,
padx=5,
pady=5,
text="Submit",
font=("Arial", 20),
command= lambda:countdown(countdownBox.get())
)
timerFrame.pack()
timerFrameText.pack()
countdownBox.pack()
submitCountdown.pack()
root.mainloop()
You can try this one I've implemented Threading. This allows you to run threads or let's say processes synchronously. In creating GUI applications and such as timers or any other program that needs to be run all at the same time it's really important that to learn Multithreading and MultiProcessing. It's a really huge factor in software development.
import time
from tkinter import *
import threading
root = Tk()
def cd(timer_label_obj,ts):
while ts > 0:
timer_label_obj.config(text=ts)
ts-=1
timer_label_obj.pack()
time.sleep(1)
if ts ==0:
timer_label_obj.destroy()
completeTimer = Label(timerFrame, text="Time is complete")
completeTimer.pack()
def countdown(t):
timer = Label(timerFrame)
ts = int(t)
th = threading.Thread(target=cd,args=[timer,ts])
th.start()
timerFrame = LabelFrame(root, padx=50, pady=50, bd=0)
timerFrameText = Label(timerFrame,
text="Enter time in seconds for countdown",
font=("Arial", 20, "bold")
)
countdownBox= Entry(timerFrame, bd=3)
submitCountdown = Button(timerFrame,
padx=5,
pady=5,
text="Submit",
font=("Arial", 20),
command= lambda:countdown(countdownBox.get())
)
timerFrame.pack()
timerFrameText.pack()
countdownBox.pack()
submitCountdown.pack()
root.mainloop()
Maybe adding some kind of print statement with your ts value will help. Never used tkinter, but it looks like you only print when it finishes count down. Or try to print ts as a string like in a normal print statment:
print("current count %s" %ts)
Related
I copied this code to try to make a countdown app in tkiner. Whenever I run the code and click the generated button, it just just keeps loading, and I have to force quit. I am wondering if there is something wrong with this code? Everything seems to generate fine, the only problems seems to be the button click and execution.
import time
from tkinter import *
from tkinter import messagebox
root = Tk()
root.geometry("300x250")
root.title("Time Counter")
hour=StringVar()
minute=StringVar()
second=StringVar()
hour.set("00")
minute.set("00")
second.set("00")
hourEntry= Entry(root, width=3, font=("Arial",18,""),
textvariable=hour)
hourEntry.place(x=80,y=20)
minuteEntry= Entry(root, width=3, font=("Arial",18,""),
textvariable=minute)
minuteEntry.place(x=130,y=20)
secondEntry= Entry(root, width=3, font=("Arial",18,""),
textvariable=second)
secondEntry.place(x=180,y=20)
def submit():
try:
temp = int(hour.get())*3600 + int(minute.get())*60 + int(second.get())
except:
print("Please input the right value")
while temp >-1:
mins,secs = divmod(temp,60)
hours=0
if mins >60:
hours, mins = divmod(mins, 60)
hour.set("{0:2d}".format(hours))
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's up ")
temp -= 1
btn = Button(root, text='Set Time Countdown', bd='5',
command= submit)
btn.place(x = 70,y = 120)
root.mainloop()
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.
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
I am new to Python and just programming in general. I need help to create a countdown display to my timer which is a tequila timer. My friends drink a lot of Tequila and asked me to make them a timer, because they know nothing about computers.
Anyways, I have made the layout the best way I could do it and it looks like this: https://i.stack.imgur.com/bvt57.png
What I need help with is that when the 1 minute button is clicked, it counts down from one minute to 0. And the same goes with the other button. I also need help with the stop ruction, so you can stop the timer at any time by pressing the stop button. It would really be a big help!!
Here is my code:
import tkinter as tk
#GUI
root = tk.Tk()
root.title("Tequila timer")
#Load of background with the tequila bottle
canvas = tk.Canvas(root, width=423, height=700)
canvas.pack()
Load = tk.PhotoImage(file="tequila.png")
canvas.create_image(211, 350, image=Load)
#buttons
btn_1min = tk.Button(root, text="1 min", width=10, height=5, command=root.quit)
btn_1min_v = canvas.create_window(140, 350, window=btn_1min)
btn_10min = tk.Button(root, text="10 min", width=10, height=5, command=root.quit)
bt1_10min_v = canvas.create_window(283, 350, window=btn_10min)
btn_1hour = tk.Button(root, text="1 hour", width=10, height=5, command=root.quit)
bt1_1hour_v = canvas.create_window(140, 475, window=btn_1hour)
btn_2hours = tk.Button(root, text="2 hours", width=10, height=5, command=root.quit)
bt1_2hours_v = canvas.create_window(283, 475, window=btn_2hours)
btn_stop = tk.Button(root, text="Stop", width=10, height=5, command=root.quit)
bt1_stop_v = canvas.create_window(211, 600, window=btn_stop)
#Display
label = tk.Label(root, text="00:00:00", width=9, font=("calibri", 40, "bold"))
label.pack()
label_v = canvas.create_window(211, 200, window=label)
root.mainloop()
The problem is none of your buttons are mapped to anything. Well, other than root.quit which just closes your window.
So lets start with your first problem. You need to update the text in your Label. A good way to do that is with tkinters StringVar class. Updating a StringVar will also update the text in your Label so lets do that first.
countdown = tk.StringVar()
countdown.set("00:00:00")
and then set it as the text in your Label.
label = tk.Label(root, textvariable=countdown, width=9, font=("calibri", 40, "bold"))
Now that that's taken care of. lets move on to your second problem. Your lack of call back functions. First lets set us up with a function to call that accepts a variable amount of seconds to count down from.
To display something after root.mainloop() has been called we can use the after() method. It will return an identifier that we can use to cancel the update later. However, in order to use it in another function later to cancel, we should make it a global variable with the keyword global.
The root.after takes a milliseconds argument and a function to call after those milliseconds have elapsed. Lets call this function again with one fewer milliseconds than it was called with. also, if the seconds have dipped below 0 we should cancel the callback.
We can do that with root.after_cancel and handing it the after identifier we retrieved from root.after before.
Another thing to note is that root.afters function argument expects a function object. To hand it a function with an argument we can wrap it in a lambda.
def update(seconds):
global after
if seconds >= 0:
countdown.set(seconds_to_time(seconds))
after = root.after(1000, lambda: update(seconds - 1))
else:
root.after_cancel(after)
But what is this seconds_to_time function were setting our StringVar to? Well, it's just a little helper to make our seconds display in a proper hh:mm:ss format.
def seconds_to_time(seconds):
hours = seconds // 3600
seconds -= hours * 3600
minutes = seconds // 60
seconds -= minutes * 60
return f'{hours:02d}:{minutes:02d}:{seconds:02d}'
And your final request is to stop the countdown. We can do that by just cancelling our global after identifier. It's a good idea to make sure we've set the after first by avoiding NameError errors in case it hasn't been set yet.
def stop():
try:
root.after_cancel(after)
except NameError:
pass
Full code below:
note the callback functions for your buttons are wrapped in lambdas to allow us to hand an argument to the callback.
import tkinter as tk
def update(seconds):
global after
if seconds >= 0:
countdown.set(seconds_to_time(seconds))
after = root.after(1000, lambda: update(seconds - 1))
else:
root.after_cancel(after)
def seconds_to_time(seconds):
hours = seconds // 3600
seconds -= hours * 3600
minutes = seconds // 60
seconds -= minutes * 60
return f'{hours:02d}:{minutes:02d}:{seconds:02d}'
def stop():
try:
root.after_cancel(after)
except NameError:
pass
#GUI
root = tk.Tk()
root.title("Tequila timer")
#Load of background with the tequila bottle
canvas = tk.Canvas(root, width=423, height=700)
canvas.pack()
Load = tk.PhotoImage(file="tequila.png")
canvas.create_image(211, 350, image=Load)
countdown = tk.StringVar()
countdown.set("00:00:00")
#buttons
btn_1min = tk.Button(root, text="1 min", width=10, height=5, command=lambda: update(60))
btn_1min_v = canvas.create_window(140, 350, window=btn_1min)
btn_10min = tk.Button(root, text="10 min", width=10, height=5, command=lambda: update(600))
bt1_10min_v = canvas.create_window(283, 350, window=btn_10min)
btn_1hour = tk.Button(root, text="1 hour", width=10, height=5, command=lambda: update(3600))
bt1_1hour_v = canvas.create_window(140, 475, window=btn_1hour)
btn_2hours = tk.Button(root, text="2 hours", width=10, height=5, command=lambda: update(7200))
bt1_2hours_v = canvas.create_window(283, 475, window=btn_2hours)
btn_stop = tk.Button(root, text="Stop", width=10, height=5, command=stop)
bt1_stop_v = canvas.create_window(211, 600, window=btn_stop)
#Display
label = tk.Label(root, textvariable=countdown, width=9, font=("calibri", 40, "bold"))
label.pack()
label_v = canvas.create_window(211, 200, window=label)
root.mainloop()
I have a large code where a button press is supposed to run a code that will take roughly 15 seconds to complete. Within this time I want to display a label that says "Processing, please wait" or something of that sort. However in python, the whole GUI created using tkinter will freeze and unfreeze once the procedure is over. How do I get around to doing this? I created a smaller code so that I can explain easier.
from tkinter import *
from threading import Thread
import os
import sys
import time
master = Tk()
master.geometry("500x500")
master.resizable(False,False)
def tryout():
sign2.config(text = "AAA")
for x in range(5):
print(x)
time.sleep(1)
sign2.config(text = "BBB")
for x in range(5):
print(x)
time.sleep(1)
sign2.config(text = "CCC")
def close_window():
master.destroy()
sys.exit()
sign1 = Label(master, text = "VNA GUI").grid(pady=10, padx=10)
sign2 = Label(master, text = "Choose option to continue")
sign2.grid(pady=10, padx=10, ipadx=50)
Button(master, text='Exit', command=close_window).grid(pady=10, padx=20)
butTest = Button(master, text='test', command=tryout)
butTest.grid(pady=10, padx=20)
master.mainloop( )
So in this code I expect to see 'AAA' on the label first, followed by 'BBB' at the middle of the count from 0 to 4, and then 'CCC' at the end of the final count from 0 to 4. What happens here is the GUI freezes at the beginning, the count carries on and I just see 'CCC'. How do I get around this?
There are only a few changes necessary to do that with threading.
First create a function start_tryout:
def start_tryout():
Thread(target=tryout, daemon=True).start() # deamon=True is important so that you can close the program correctly
Then create the button with the new command:
butTest = Button(master, text='test', command=start_tryout)
Then it should no longer freeze the gui and you should be able to see the label change.
You can try threading. I've made changes below to the code and tested it here, and it worked.
from tkinter import *
from threading import Thread
import os
import sys
import time
import threading # NEW
master = Tk()
master.geometry("500x500")
master.resizable(False,False)
def tryout():
sign2.config(text = "AAA")
for x in range(5):
print(x)
time.sleep(1)
sign2.config(text = "BBB")
for x in range(5):
print(x)
time.sleep(1)
sign2.config(text = "CCC")
def close_window():
master.destroy()
sys.exit()
def thread(): # NEW
threading.Thread(target=tryout).start() # NEW
sign1 = Label(master, text = "VNA GUI").grid(pady=10, padx=10)
sign2 = Label(master, text = "Choose option to continue")
sign2.grid(pady=10, padx=10, ipadx=50)
Button(master, text='Exit', command=close_window).grid(pady=10, padx=20)
butTest = Button(master, text='test', command=thread) # Changed
butTest.grid(pady=10, padx=20)
master.mainloop( )