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()
Related
I would like to make simple click speed test in Python (tkinter).My actual code looks like that:
from tkinter import *
import time
def coutdown():
t = 5
while t:
mins, secs = divmod(t, 60)
timer = '{:02d}:{:02d}'.format(mins, secs)
print(timer, end="\r")
time.sleep(1)
t -= 1
if t==0:
print("Finish !!!")
break
#window configuration
screen= Tk()
screen.title("Click Speed Test")
screen.configure(width= 500, height= 300)
screen.configure(bg='lightblue')
canvas = Canvas(screen, width=500, height=500)
#text
text= Label(screen, text= "Click Speed Test")
text.config(font= ("Courier", 16))
text.pack()
text_v2= Label(screen, text= "Click the button to start the game!")
text_v2.config(font= ("Courier", 10))
text_v2.pack(ipady= 10)
# start button
turn_on= Button(screen, text="Start", command= coutdown)
turn_on.pack(ipadx=10, ipady= 5, pady= 5, padx= 100)
#click button
click= Button(screen, text="Click!!!")
click.pack(ipadx=6, ipady= 8, pady= 5, padx= 5)
screen.mainloop()
I have big problem with buttons. I made fuction: coutdown, which is to calculate time and I set it as a command to start button. Second button will be use to click on it after start the game, but now it hasn't a command. My vision look like that: When I will start the game by "Start" button, I will have 5 seconds to click "Click" button as many as I can. After 5 seconds script will shut down and and the result will display. But now if I click start button, all script freezes and I can't click any button. Do you know how to fix it?
I tried to change my timer but all the time script works wrong.
Hope this helps:
from tkinter import *
import time
num = 0
tm = 0
def count():
global num
if tm:
num += 1
def coutdown():
global tm
tm = time.time()
def refresh():
global tm, num
dif = time.time() - tm
if tm and dif > 5:
print("Finish !!!", num, 'clicks')
num = 0
tm = 0
elif tm:
mins, secs = divmod(int(dif), 60)
timer = '{:02d}:{:02d}'.format(mins, secs)
print(timer, end="\r")
screen.after(1, refresh)
#window configuration
screen= Tk()
screen.title("Click Speed Test")
screen.configure(width= 500, height= 300)
screen.configure(bg='lightblue')
canvas = Canvas(screen, width=500, height=500)
#text
text= Label(screen, text= "Click Speed Test")
text.config(font= ("Courier", 16))
text.pack()
text_v2= Label(screen, text= "Click the button to start the game!")
text_v2.config(font= ("Courier", 10))
text_v2.pack(ipady= 10)
# start button
turn_on= Button(screen, text="Start", command= coutdown)
turn_on.pack(ipadx=10, ipady= 5, pady= 5, padx= 100)
#click button
click= Button(screen, text="Click!!!", command= count)
click.pack(ipadx=6, ipady= 8, pady= 5, padx= 5)
#screen.mainloop()
refresh_var = screen.after_idle(refresh)
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 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()
I have just started playing with tkinter today and I have two bit of code I have been playing with as examples however I am struggling to combine these can anyone advise. I would like to clock to display in the main window.
import tkinter
from tkinter import *
import sys
import time
root = Tk()
root.title('Logging')
Label(text='Time logging').pack(side=TOP,padx=100,pady=100)
entry = Entry(root, width=25)
entry.pack(side=TOP,padx=25,pady=25)
def onok():
x, y = entry.get().split('x')
for row in range(int(y)):
for col in range(int(x)):
print((col, row))
Button(root, text='Log Time', command=onok).pack(side=LEFT)
Button(root, text='CLOSE').pack(side= RIGHT)
def tick():
global time1
# get the current local time from the PC
time2 = time.strftime('%H:%M:%S')
# if time string has changed, update it
if time2 != time1:
time1 = time2
clock.config(text=time2)
# calls itself every 200 milliseconds
# to update the time display as needed
# could use >200 ms, but display gets jerky
clock.after(200, tick)
root = Tk()
time1 = ''
status = Label(root, text="v1.0", bd=1, relief=SUNKEN, anchor=W)
status.grid(row=10, column=10)
clock = Label(root, font=('times', 20, 'bold'), bg='green')
clock.grid(row=0, column=1)
tick()
root.mainloop()
You can use Frame to group clock widgets and use grid inside this frame. And frame you can put in main window. (And you don't need second Tk())
I put it at the top but you can choose other place.
import tkinter as tk
import time
# --- functions ---
def on_ok():
x, y = entry.get().split('x')
for row in range(int(y)):
for col in range(int(x)):
print((col, row))
def tick():
global time1
# get the current local time from the PC
time2 = time.strftime('%H:%M:%S')
# if time string has changed, update it
if time2 != time1:
time1 = time2
clock.config(text=time2)
# calls itself every 200 milliseconds
# to update the time display as needed
# could use >200 ms, but display gets jerky
clock.after(200, tick)
# --- main window ---
time1 = ''
root = tk.Tk()
root.title('Logging')
# add frame in main window (root)
other = tk.Frame(root)
other.pack()
# put widgets in frame (other)
status = tk.Label(other, text="v1.0", bd=1, relief=tk.SUNKEN, anchor=tk.W)
status.grid(row=10, column=10)
clock = tk.Label(other, font=('times', 20, 'bold'), bg='green')
clock.grid(row=0, column=1)
# put other widget directly in main widnow (root)
tk.Label(root, text='Time logging').pack(side=tk.TOP, padx=100, pady=100)
entry = tk.Entry(root, width=25)
entry.pack(side=tk.TOP, padx=25, pady=25)
tk.Button(root, text='Log Time', command=on_ok).pack(side=tk.LEFT)
tk.Button(root, text='CLOSE', command=root.destroy).pack(side= tk.RIGHT)
tick()
# --- start ---
root.mainloop()
You have two different root windows. Use one root = Tk() line at the top and you should have them on the same page.