Python | How do i make a Fast Reaction test with Tkinter? - python

I tried making a fast reaction tester with Tkinter Module in Python, but when I clicked the Start button, it justs freezes the window. And I don't know how to recover that
Here's my code:
import webbrowser as wb
import time
import math
from random import *
from tkinter import *
from PIL import ImageTk, Image
seconds = 0
miliseconds = 0
minutes = 0
def reactionStarted():
global seconds, miliseconds, greenimages, redimages, minutes
# Put Image Green
reactionImage.config(image=greenimages)
# Random Countdown
countdownSecond = randint(4, 9)
countdownMiliSecond = randint(0, 9)
# Turn into float ( More Randomized )
countdownBonk = float(str(countdownSecond) + "." + str(countdownMiliSecond))
# Start Countdown
print(countdownBonk) # i was testing if this was the problem but its not
time.sleep(countdownBonk)
# Red image ( fast reaction part )
reactionImage.config(image=redimages)
# Timer
timeLoop = True
while timeLoop:
miliseconds += 1
time.sleep(0.1)
if miliseconds == 10:
seconds += 1
miliseconds = 0
elif seconds == 60:
seconds = 0
minutes += 1
def reactionCompleted():
global seconds, miliseconds, minutes
timeLoop = False
if not timeLoop:
reactionImage.config(image='', text=(
str(minutes) + "Minute(s)" + str(seconds) + "Second(s)" + str(miliseconds) + "Milisecond(s)"))
root = Tk()
root.title("Fast Reaction Test")
greenimages = ImageTk.PhotoImage(Image.open("green.png"))
redimages = ImageTk.PhotoImage(Image.open("red.png"))
reactionImage = Label(text='Click the button Below To Start!')
reactionImage.pack()
Start = Button(root, width=500, height=5, text="Click Here to Start", command=reactionStarted)
Start.pack()
Stop = Button(root, width=500, height=10, text="Stop (Spacebar)", command=reactionCompleted)
Stop.bind("<space>", reactionCompleted)
Stop.focus_force()
Stop.pack()
root.mainloop()
Really, thanks if you helped me out :)

Your error is that you are asking your program to enter an infinite loop when clicking the start button. The interpreter never leaves that loop, and thus the UI gets stuck without being able to update itself or receive input, because the interpreter is still stuck within your loop.
So you need to have another approach for this. Issues like these are normally handled by opening separate threads in your program, that can execute independently such that your main thread responsible for updating the UI window is not impacted by the child thread running your infinite loop. Then the main thread can at some point send a message to the child thread that the loop should be cancelled, when you user presses the stop button.
Handling this in tkinter has been made easy with the after() method, which simply put creates such an infinite loop in a separate thread to allow the main thread to keep running. I have below included a small example of how such an after loop can look, and you can try implementing that in your own code. If you still have problems, open a new question with more clarity.
import tkinter as tk
import time
class Timer:
def __init__(self):
self.root = tk.Tk()
self.sv = tk.StringVar()
self.start_time = None
self.after_loop = None
self.make_widgets()
self.root.mainloop()
def make_widgets(self):
tk.Label(self.root, textvariable=self.sv).pack()
tk.Button(self.root, text='start', command=self.start).pack()
tk.Button(self.root, text='stop', command=self.stop).pack()
def start(self):
self.start_time = time.time()
self.timer()
def timer(self):
self.sv.set(round(time.time() - self.start_time))
self.after_loop = self.root.after(500, self.timer)
def stop(self):
if self.after_loop is not None:
self.root.after_cancel(self.after_loop)
self.after_loop = None
Timer()

Related

tkinter .after() second and minute

hey guys i have a problem im making a timer in tkinter but i cant use time.sleep() so i use .after() and i have new problem,I made an entry that I want the entry number to be * 60 and after the set time, a text will be written that says >> time is over! ,but then, how should that 60 be converted into seconds? my code:
from tkinter import *
from playsound import playsound
from time import sleep
import time
def jik():
a = int(text.get())
app.after(a * 600)
Label(app,text="time is over").pack()
app = Tk()
app.minsize(300,300)
app.maxsize(300,300)
text = Entry(app,font=20)
text.pack()
Button(app,text="start",command=jik).pack()
app.mainloop()
For example, if I press the number 1, it >>time is over in a fraction of a second
The after command takes input in milliseconds, so multiply it by 1000 to convert it to seconds.
Additionally, I just made a small example that displays the countdown for you as the clock ticks down:
# Usually it is a good idea to refrain from importing everything from the tkinter
# package, as to not pollute your namespace
import tkinter as tk
root = tk.Tk() # Customary to call your Tk object root
entry = tk.Entry(root)
entry.pack()
curtime = tk.Label(root)
curtime.pack()
is_running = False # Global variable to ensure we can't run two timers at once
def countdown(count):
global is_running
if count > 0:
curtime['text'] = f'{count:.2f}' # Update label
# Call the countdown function recursively until timer runs out
root.after(50, countdown, count-0.05)
else:
curtime['text'] = 'time is over'
is_running = False
def btn_press():
global is_running
if is_running:
return
cnt = int(entry.get())
is_running = True
countdown(cnt)
tk.Button(root, text='start', command=btn_press).pack()
root.minsize(300, 300)
root.maxsize(300, 300)
root.mainloop()
.after function takes two arguments, the first is the time in milliseconds, that is 1000 milliseconds is equal to one second, the second argument is a function to call after that time has passed, simply define what to do after the time has passed in a second function, and use it as a second argument as follows.
from tkinter import *
from playsound import playsound
from time import sleep
import time
MILLISECOND_TO_SECOND = 1000
def jik():
a = int(text.get())
app.after(a * MILLISECOND_TO_SECOND, show_label)
def show_label():
Label(app,text="time is over").pack()
app = Tk()
app.minsize(300,300)
app.maxsize(300,300)
text = Entry(app,font=20)
text.pack()
Button(app,text="start",command=jik).pack()
app.mainloop()

Tkinter app does not update timer smoothly

I am pretty new to using Tkinter and I am trying to build an app that displays a timer as one of its features. I am updating a label to display the time from a separate thread. On the display the time does not update smoothly. It freezes for short periods of time and then jumps a second or more. It there a way to keep the updates consistent so the timer is smooth?
It's a simple app so far so it doesn't seem like the CPU or main thread should be busy doing anything else. When you press a button it starts a separate thread that periodically sets the label text. I've tried sleeping between 0-0.1 seconds per update and the result is the same.
window = tk.Tk()
frame2 = tk.Frame(master=window, width=50, height=50, bg="yellow")
frame2.pack()
time_display = tk.Label(master=frame2, text="0.0")
time_display.pack()
update_thread = None
def play_pause():
global update_thread, stop_loop
if not update_thread:
stop_loop = False
update_thread = threading.Thread(target=update_timer_loop)
update_thread.start()
else:
stop_loop = True
update_thread = None
stop_loop = False
def update_timer_loop():
global window
start = time.time()
base_time = float(time_display["text"])
while not stop_loop:
current_time = time.time() - start + base_time
window.after(0, lambda: set_text(round(current_time, 2)))
time.sleep(0.1)
def set_text(text):
time_display["text"] = text
btn_play = tk.Button(master=frame1, text="Play/Pause", command=play_pause)
btn_play.pack(side=tk.LEFT)
You don't need threading at all in this. I see you tried to use after, and that's the correct approach. The only thing extra to know is that you can use after_cancel to cancel an upcoming event that you scheduled with after. Try this:
import tkinter as tk
import time
window = tk.Tk()
time_display = tk.Label(window, text="0.0")
time_display.pack()
update_thread = None
def play_pause():
global update_thread, start, base_time
if update_thread is None:
start = time.time()
base_time = float(time_display["text"])
update_timer_loop() # start the loop
else:
time_display.after_cancel(update_thread)
update_thread = None
def update_timer_loop():
global update_thread
current_time = time.time() - start + base_time
time_display["text"] = round(current_time, 2)
update_thread = window.after(100, update_timer_loop)
btn_play = tk.Button(master=window, text="Play/Pause", command=play_pause)
btn_play.pack(side=tk.LEFT)
window.mainloop()

How to temporarily pause a GUI in Tkinter? [duplicate]

I'm writing a program with Python's tkinter library.
My major problem is that I don't know how to create a timer or a clock like hh:mm:ss.
I need it to update itself (that's what I don't know how to do); when I use time.sleep() in a loop the whole GUI freezes.
Tkinter root windows have a method called after which can be used to schedule a function to be called after a given period of time. If that function itself calls after you've set up an automatically recurring event.
Here is a working example:
# for python 3.x use 'tkinter' rather than 'Tkinter'
import Tkinter as tk
import time
class App():
def __init__(self):
self.root = tk.Tk()
self.label = tk.Label(text="")
self.label.pack()
self.update_clock()
self.root.mainloop()
def update_clock(self):
now = time.strftime("%H:%M:%S")
self.label.configure(text=now)
self.root.after(1000, self.update_clock)
app=App()
Bear in mind that after doesn't guarantee the function will run exactly on time. It only schedules the job to be run after a given amount of time. It the app is busy there may be a delay before it is called since Tkinter is single-threaded. The delay is typically measured in microseconds.
Python3 clock example using the frame.after() rather than the top level application. Also shows updating the label with a StringVar()
#!/usr/bin/env python3
# Display UTC.
# started with https://docs.python.org/3.4/library/tkinter.html#module-tkinter
import tkinter as tk
import time
def current_iso8601():
"""Get current date and time in ISO8601"""
# https://en.wikipedia.org/wiki/ISO_8601
# https://xkcd.com/1179/
return time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
def createWidgets(self):
self.now = tk.StringVar()
self.time = tk.Label(self, font=('Helvetica', 24))
self.time.pack(side="top")
self.time["textvariable"] = self.now
self.QUIT = tk.Button(self, text="QUIT", fg="red",
command=root.destroy)
self.QUIT.pack(side="bottom")
# initial time display
self.onUpdate()
def onUpdate(self):
# update displayed time
self.now.set(current_iso8601())
# schedule timer to call myself after 1 second
self.after(1000, self.onUpdate)
root = tk.Tk()
app = Application(master=root)
root.mainloop()
from tkinter import *
import time
tk=Tk()
def clock():
t=time.strftime('%I:%M:%S',time.localtime())
if t!='':
label1.config(text=t,font='times 25')
tk.after(100,clock)
label1=Label(tk,justify='center')
label1.pack()
clock()
tk.mainloop()
You should call .after_idle(callback) before the mainloop and .after(ms, callback) at the end of the callback function.
Example:
import tkinter as tk
import time
def refresh_clock():
clock_label.config(
text=time.strftime("%H:%M:%S", time.localtime())
)
root.after(1000, refresh_clock) # <--
root = tk.Tk()
clock_label = tk.Label(root, font="Times 25", justify="center")
clock_label.pack()
root.after_idle(refresh_clock) # <--
root.mainloop()
I have a simple answer to this problem. I created a thread to update the time. In the thread i run a while loop which gets the time and update it. Check the below code and do not forget to mark it as right answer.
from tkinter import *
from tkinter import *
import _thread
import time
def update():
while True:
t=time.strftime('%I:%M:%S',time.localtime())
time_label['text'] = t
win = Tk()
win.geometry('200x200')
time_label = Label(win, text='0:0:0', font=('',15))
time_label.pack()
_thread.start_new_thread(update,())
win.mainloop()
I just created a simple timer using the MVP pattern (however it may be
overkill for that simple project). It has quit, start/pause and a stop button. Time is displayed in HH:MM:SS format. Time counting is implemented using a thread that is running several times a second and the difference between the time the timer has started and the current time.
Source code on github
from tkinter import *
from tkinter import messagebox
root = Tk()
root.geometry("400x400")
root.resizable(0, 0)
root.title("Timer")
seconds = 21
def timer():
global seconds
if seconds > 0:
seconds = seconds - 1
mins = seconds // 60
m = str(mins)
if mins < 10:
m = '0' + str(mins)
se = seconds - (mins * 60)
s = str(se)
if se < 10:
s = '0' + str(se)
time.set(m + ':' + s)
timer_display.config(textvariable=time)
# call this function again in 1,000 milliseconds
root.after(1000, timer)
elif seconds == 0:
messagebox.showinfo('Message', 'Time is completed')
root.quit()
frames = Frame(root, width=500, height=500)
frames.pack()
time = StringVar()
timer_display = Label(root, font=('Trebuchet MS', 30, 'bold'))
timer_display.place(x=145, y=100)
timer() # start the timer
root.mainloop()
You can emulate time.sleep with tksleep and call the function after a given amount of time. This may adds readability to your code, but has its limitations:
def tick():
while True:
clock.configure(text=time.strftime("%H:%M:%S"))
tksleep(0.25) #sleep for 0.25 seconds
root = tk.Tk()
clock = tk.Label(root,text='5')
clock.pack(fill=tk.BOTH,expand=True)
tick()
root.mainloop()

How can I have a ttk.progressbar call a function when it is completed (Python)?

I am trying to have a ttk progressbar call a function that adds to a value that is displayed in the Tkinter window. I currently have a function that is called at the same time the progressbar begins:
def bar1_addVal():
global userMoney
userMoney += progBar1_values.value
moneyLabel["text"] = ('$' + str(userMoney))
canvas1.after((progBar1_values.duration*100), bar1_addVal)
return
but I cannot seem to get the exact amount of time it takes for the progressbar to finish each iteration. Is there a way to have the progressbar call a function every time it completes?
You can use threading to check for the variable in a loop. Then you wont interrupt the main loop.
I made a little example of this:
import threading, time
from ttk import Progressbar, Frame
from Tkinter import IntVar, Tk
root = Tk()
class Progress:
val = IntVar()
ft = Frame()
ft.pack(expand=True)
kill_threads = False # variable to see if threads should be killed
def __init__(self):
self.pb = Progressbar(self.ft, orient="horizontal", mode="determinate", variable=self.val)
self.pb.pack(expand=True)
self.pb.start(50)
threading.Thread(target=self.check_progress).start()
def check_progress(self):
while True:
if self.kill_threads: # if window is closed
return # return out of thread
val = self.val.get()
print(val)
if val > 97:
self.finish()
return
time.sleep(0.1)
def finish(self):
self.ft.pack_forget()
print("Finish!")
progressbar = Progress()
def on_closing(): # function run when closing
progressbar.kill_threads = True # set the kill_thread attribute to tru
time.sleep(0.1) # wait to make sure that the loop reached the if statement
root.destroy() # then destroy the window
root.protocol("WM_DELETE_WINDOW", on_closing) # bind a function to close button
root.mainloop()
Edit: Updated the answer, to end the thread before closing window.

How can I schedule updates (f/e, to update a clock) in tkinter?

I'm writing a program with Python's tkinter library.
My major problem is that I don't know how to create a timer or a clock like hh:mm:ss.
I need it to update itself (that's what I don't know how to do); when I use time.sleep() in a loop the whole GUI freezes.
Tkinter root windows have a method called after which can be used to schedule a function to be called after a given period of time. If that function itself calls after you've set up an automatically recurring event.
Here is a working example:
# for python 3.x use 'tkinter' rather than 'Tkinter'
import Tkinter as tk
import time
class App():
def __init__(self):
self.root = tk.Tk()
self.label = tk.Label(text="")
self.label.pack()
self.update_clock()
self.root.mainloop()
def update_clock(self):
now = time.strftime("%H:%M:%S")
self.label.configure(text=now)
self.root.after(1000, self.update_clock)
app=App()
Bear in mind that after doesn't guarantee the function will run exactly on time. It only schedules the job to be run after a given amount of time. It the app is busy there may be a delay before it is called since Tkinter is single-threaded. The delay is typically measured in microseconds.
Python3 clock example using the frame.after() rather than the top level application. Also shows updating the label with a StringVar()
#!/usr/bin/env python3
# Display UTC.
# started with https://docs.python.org/3.4/library/tkinter.html#module-tkinter
import tkinter as tk
import time
def current_iso8601():
"""Get current date and time in ISO8601"""
# https://en.wikipedia.org/wiki/ISO_8601
# https://xkcd.com/1179/
return time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
def createWidgets(self):
self.now = tk.StringVar()
self.time = tk.Label(self, font=('Helvetica', 24))
self.time.pack(side="top")
self.time["textvariable"] = self.now
self.QUIT = tk.Button(self, text="QUIT", fg="red",
command=root.destroy)
self.QUIT.pack(side="bottom")
# initial time display
self.onUpdate()
def onUpdate(self):
# update displayed time
self.now.set(current_iso8601())
# schedule timer to call myself after 1 second
self.after(1000, self.onUpdate)
root = tk.Tk()
app = Application(master=root)
root.mainloop()
from tkinter import *
import time
tk=Tk()
def clock():
t=time.strftime('%I:%M:%S',time.localtime())
if t!='':
label1.config(text=t,font='times 25')
tk.after(100,clock)
label1=Label(tk,justify='center')
label1.pack()
clock()
tk.mainloop()
You should call .after_idle(callback) before the mainloop and .after(ms, callback) at the end of the callback function.
Example:
import tkinter as tk
import time
def refresh_clock():
clock_label.config(
text=time.strftime("%H:%M:%S", time.localtime())
)
root.after(1000, refresh_clock) # <--
root = tk.Tk()
clock_label = tk.Label(root, font="Times 25", justify="center")
clock_label.pack()
root.after_idle(refresh_clock) # <--
root.mainloop()
I have a simple answer to this problem. I created a thread to update the time. In the thread i run a while loop which gets the time and update it. Check the below code and do not forget to mark it as right answer.
from tkinter import *
from tkinter import *
import _thread
import time
def update():
while True:
t=time.strftime('%I:%M:%S',time.localtime())
time_label['text'] = t
win = Tk()
win.geometry('200x200')
time_label = Label(win, text='0:0:0', font=('',15))
time_label.pack()
_thread.start_new_thread(update,())
win.mainloop()
I just created a simple timer using the MVP pattern (however it may be
overkill for that simple project). It has quit, start/pause and a stop button. Time is displayed in HH:MM:SS format. Time counting is implemented using a thread that is running several times a second and the difference between the time the timer has started and the current time.
Source code on github
from tkinter import *
from tkinter import messagebox
root = Tk()
root.geometry("400x400")
root.resizable(0, 0)
root.title("Timer")
seconds = 21
def timer():
global seconds
if seconds > 0:
seconds = seconds - 1
mins = seconds // 60
m = str(mins)
if mins < 10:
m = '0' + str(mins)
se = seconds - (mins * 60)
s = str(se)
if se < 10:
s = '0' + str(se)
time.set(m + ':' + s)
timer_display.config(textvariable=time)
# call this function again in 1,000 milliseconds
root.after(1000, timer)
elif seconds == 0:
messagebox.showinfo('Message', 'Time is completed')
root.quit()
frames = Frame(root, width=500, height=500)
frames.pack()
time = StringVar()
timer_display = Label(root, font=('Trebuchet MS', 30, 'bold'))
timer_display.place(x=145, y=100)
timer() # start the timer
root.mainloop()
You can emulate time.sleep with tksleep and call the function after a given amount of time. This may adds readability to your code, but has its limitations:
def tick():
while True:
clock.configure(text=time.strftime("%H:%M:%S"))
tksleep(0.25) #sleep for 0.25 seconds
root = tk.Tk()
clock = tk.Label(root,text='5')
clock.pack(fill=tk.BOTH,expand=True)
tick()
root.mainloop()

Categories