countdown timer overlay in python with keypress Tkinter - python

Hello I am trying to make a simple script which on key press x starts a timer of 45 sec after that when it reaches 10 sec color text changes to red and when countdown comes 0 I want to destroy the timer gui but not the program, and when i press x again program starts doing stuff again and repeats the process
So far I managed this I tried all day I also tried adding on keypress but it become so complicated and didn't worked so I am asking help here
from tkinter import *
root = Tk()
root.geometry("112x55")
root.overrideredirect(True)
root.lift()
root.wm_attributes("-topmost", True)
root.wm_attributes("-disabled", True)
root.wm_attributes("-transparentcolor", "white")
root.resizable(0, 0)
seconds = 45
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:
seconds.delete("1.0","end")
frames = Frame(root, width=500, height=500)
frames.pack()
time = StringVar()
timer_display = Label(root, font=('Trebuchet MS', 30, 'bold'))
timer_display.place(x=0, y=0)
timer() # start the timer
root.mainloop()

As you used wm_attributes('-transparentcolor', 'white') (but you never set the background color of root and the timer_display to white, so it don't have effect) and overrideredirect(True), that means you want the window totally transparent and borderless. However this has side effect that you may never get the window focus back after the window loses focus.
Also you used wm_attributes('-disabled', True), then you can't have any key press event triggered. It should not be used.
Suggest to use wm_attributes('-alpha', 0.01) to simulate the transparent effect without the above issues.
Below is an example:
from tkinter import *
root = Tk()
root.geometry("+0+0")
root.overrideredirect(True)
root.wm_attributes("-topmost", True)
root.wm_attributes("-alpha", 0.01)
root.resizable(0, 0)
seconds = 45
def countdown(seconds):
if seconds > 0:
mins, secs = divmod(seconds, 60)
timer_display.config(text="{:02d}:{:02d}".format(mins, secs),
fg='red' if seconds <= 10 else 'white')
root.after(1000, countdown, seconds-1)
else:
root.wm_attributes('-alpha', 0.01) # make the window "disappear"
def start_countdown(event):
root.wm_attributes('-alpha', 0.7) # make the window "appear"
countdown(seconds)
timer_display = Label(root, font=('Trebuchet MS', 30, 'bold'), bg='black')
timer_display.pack()
root.bind('x', start_countdown)
root.bind('q', lambda e: root.destroy()) # provide a way to close the window
root.mainloop()
NOTE #1: If the window loses focus, you can still click somewhere near the top-left corner of the screen to resume window focus, although you cannot see the window.
NOTE #2: If you want system-wise key handler, tkinter does not support it. You need to use other module, like pynput.

If you want to destroy the timer GUI, You'll need to make a class like this:
class Timer(Frame):
def __init__(self, master):
super().__init__(master)
# Paste the code you want to run here, make sure you put "self." before it.
# For example:
def clicked():
print('Clicked!')
self.myButton = Button(self, text="Click me!", border=0, width=25, height=1, command=self.clicked)
self.logbtn.grid(columnspan=2)
self.pack()
if seconds == 0:
self.destroy() # by using self.destroy(), you tell it to delete the class.
else:
# You can put whatever you want it to do if it's not 0 here.
tm = Timer(root)

You can bind functions to keystroke events with root.bind(event, callback).
If you are using Linux or Mac, root.overrideredirect(True) will
prevent your application from receiving keystroke events. You can read
more here: Tkinter's overrideredirect prevents certain events in Mac and Linux
Example:
def keydown(e):
print(f"Key pressed: ")
print("Key code:", e.keycode)
print("Key symbol:", e.keysym)
print("Char:", e.char)
def keyup(e):
print(f"Key '{e}' released")
root.bind("<KeyPress>", keydown)
root.bind("<KeyRelease>", keyup)
root.focus_set()
Alternatively, you can also bind to specific keys with <Key-KEYSYM>, e.g. <Key-space> for the spacebar. A list with all keysyms can be found here
Some more events are listed here
Implementation example
Here is an example with a custom CountdownLabel class that is derived from tkinter.Label and automatically binds to the spacebar key event.
app.py
from countdown import CountdownLabel
from tkinter import Frame, StringVar, Tk, Button
root = Tk()
root.geometry("120x60")
root.lift()
root.wm_attributes("-topmost", True)
root.resizable(0, 0)
# Not supported on Linux and MacOS
# root.overrideredirect(True)
# root.wm_attributes("-disabled", True)
# root.wm_attributes("-transparentcolor", "white")
timer_display = CountdownLabel(root, 10, 5)
timer_display.pack(fill="both", expand=True)
timer_display.configure(background="white")
timer_display.configure(font=('Trebuchet MS', 26, 'bold'))
timer_display.focus_set()
root.mainloop()
countdown.py
from tkinter import Label
class CountdownLabel(Label):
# context : A reference to the Label in order to change the text and
# to close it later on
# duration: Total time in seconds
# critical: Length of the last timespan before the countdown finishes
# in seconds
def __init__(self, context, duration, critical):
super().__init__(context)
self.duration = duration
self.critical = critical if duration >= critical else duration
self.update_ui()
self.bound_sequence = "<Key-space>"
self.bound_funcid = self.bind(self.bound_sequence, self.get_handler())
# Returns a function for the event binding that still has access to
# the instance variables
def get_handler(self):
# Gets executed once when the counter starts through handler() and calls
# itself every second from then on to update the GUI
def tick():
self.after(1000, tick)
self.update_ui()
self.duration -= 1
# Gets executed when time left is less than <critical> (default = 10s)
# Sets the font color to red
def change_font_color():
self.configure(foreground="red")
# Destroys itself after the countdown finishes
self.after((self.critical + 1) * 1000, lambda : self.destroy())
def handler(event):
self.unbind(self.bound_sequence, self.bound_funcid)
self.bound_funcid = -1
self.bound_sequence = None
self.after((self.duration - self.critical) * 1000, change_font_color)
tick()
return handler
# Updates the displayed time in the label
def update_ui(self):
mm = self.duration // 60
ss = self.duration % 60
self.config(text="%02d:%02d" % (mm, ss))
def change_binding(self, sequence):
if self.bound_funcid > 0:
self.unbind(self.bound_sequence, self.bound_funcid)
self.bound_sequence = sequence
self.funcid = self.bind(self.bound_sequence, self.get_handler())

Related

How to make a speedrun timer stay to the front

I made an in game timer for Minecraft to use while speedrunning and it works great but the one problem is that when I make Minecraft fill my whole screen NOT FULLSCREEN just cover it the timer disappears.
I knew this would happen and I am wondering if this is possible to fix and make the pygame window go to the front even if it is blocked by an app that you are currently using.
You can do this with Tkinter:
This way, you can make a timer:
Always put to front
Transparent
Furthermore, tkinter is in the Standard library.
You can use this code:
from tkinter import *
from threading import Thread
from time import time
tk = Tk()
tk.title('Timer')
tk.wm_attributes('-topmost', 1) # put the window to front
pause = True
start = time()
time_when_paused = time() # time displayed when in pause
def restart(): # restart to 0
global pause, start
pause = False
start = time()
def toggle_pause():
global pause, start, time_when_paused
pause = not pause
if pause:
time_when_paused = time() # update the time displayed
else:
start += time() - time_when_paused # forget the time passed in pause
def timer():
while True:
if pause:
label['text'] = '%.2f' %(time_when_paused - start) # %.2f for 2 decimals
label['fg'] = 'orange'
else:
label['text'] = '%.2f' %(time() - start)
label['fg'] = 'green'
label = Label(tk, '', font=('Helvetica', 30), fg='orange')
label.grid(columnspan=2) # display the timer
# buttons
restart_button = Button(tk, text='Restart', width=20, command=restart)
restart_button.grid(padx=5, pady=5)
pause_button = Button(tk, text='Pause', width=20, command=toggle_pause)
pause_button.grid(padx=(0, 5), column=1, row=1)
timer_thread = Thread(target=timer)
timer_thread.start() # put in a thread because of...
tk.mainloop() # ...this which acts like "while window is not closed"

How to Update Tkinter Label while looking for keyboard input

I am Trying to make a tkinter based Cube Timer Program for which I have done a console based program before I wanted to improve on it by making a GUI for it... I have made some tkinter projects before and I have a decent knowledge of how stuff works the thing is I want the Label in the tkinter window to update in every second while listening for a spacebar key on press I couldn't figure out a way to do it can someone help me?
def on_press(key):
global chk,start,end,timer_label
print('{0} pressed'.format(key))
if key == Key.space:
if chk == True:
end = time.time()
chk = False
messagebox.showinfo('Your Time',"Your Time =>"+str(round(end-start,3)))
def on_release(key):
global chk,start,end,timer_label
if key == Key.space:
if chk == False:
start = time.time()
chk = True
with Listener(on_press=on_press,on_release=on_release) as listener:
main = tk.Tk()
main.title("Cube Timer Mark 4 Prototype")
main.resizable(0,0)
tk.Label(main, text="Cube Time Mk3 Prototype", font=['Consolas',16]).grid(row = 0,column=0)
textbox = tk.Text(main,font=['Consolas',20], height = 1,width = 27)
textbox.grid(row = 1,column=0)
textbox.insert('1.0',scrambler())
timer_frame = tk.Frame(main).grid(row=2,column=0)
global timer_label
timer_label = tk.StringVar()
timer_label.set("0.0")
tk.Label(timer_frame,text = timer_label.get()+"s",font=['Consolas',20,'bold'],pady = 25).grid(row = 2,column = 0)
main.mainloop()
This is what I have tried but didn't work
You need main.after to periodically run a function like updating the label. To listen to keyboard event, you need main.bind. See example below.
Enhancement: use tk.Label(..., textvariable=timer_label) means the label will automatically update when you set timer_label
import tkinter as tk
def key_press(event):
if event.keysym == 'space':
print('pressed')
def key_release(event):
if event.keysym == 'space':
print('released')
def update_label():
# get the time from the string
time = float(timer_label.get()[:-1])
# increment the time and put it back in timer_label
timer_label.set(str(time+1) + 's')
# calling this function again 1000ms later, which will call this again 1000ms later, ...
main.after(1000, update_label)
main = tk.Tk()
main.title("Cube Timer Mark 4 Prototype")
main.resizable(0,0)
tk.Label(main, text="Cube Time Mk3 Prototype", font=['Consolas',16]).grid(row = 0,column=0)
textbox = tk.Text(main,font=['Consolas',20], height = 1,width = 27)
textbox.grid(row = 1,column=0)
#textbox.insert('1.0',scrambler())
timer_frame = tk.Frame(main).grid(row=2,column=0)
timer_label = tk.StringVar()
timer_label.set("0.0s")
# using textvariable, you can simply update timer_label and it will be reflected
tk.Label(timer_frame,textvariable = timer_label ,font=['Consolas',20,'bold'],pady = 25).grid(row = 2,column = 0)
# bind keypress and keyrelease to the functions
main.bind("<KeyPress>", key_press)
main.bind("<KeyRelease>", key_release)
# call update label function for the first time
update_label()
main.mainloop()

How to create a timer with tkinter? I am learning Python [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()

Check if a Tkinter button remains clicked

I want to make 2 buttons (+ and -) that can change the volume. But if I want to increase the volume more, I'd like to can keep the button pressed. Any ideas how to check if a button remains pressed by the user?
def volumeUp():
currentVolume = m.getvolume()[0]
volumeSlider.set(currentVolume + 5)
def volumeDown():
currentVolume = m.getvolume()[0]
volumeSlider.set(currentVolume - 5)
volumeDownButton = Button(win, text = "-", font = myFont, command = volumeDown, height = 1, width = 1)
volumeDownButton.pack(side = BOTTOM)
volumeUpButton = Button(win, text = "+", font = myFont, command = volumeUp, height = 1, width = 1)
volumeUpButton.pack(side = BOTTOM)
What you can do is make the Button press fire a function that alters the volume and then schedules itself to be run again after a certain amount of time (e.g. 100 ms). Then when the Button is released, you can cancel the scheduled repeat of the function that alters the volume to break the loop.
I've altered your code a bit to make an example:
from tkinter import *
win = Tk()
def volumeUpPress(e=None):
global up_after
currentVolume = volumeSlider.get()
volumeSlider.set(currentVolume + 2)
up_after = win.after(100, volumeUpPress)
def volumeUpRelease(e=None):
global up_after
win.after_cancel(up_after)
def volumeDownPress(e=None):
global down_after
currentVolume = volumeSlider.get()
volumeSlider.set(currentVolume - 2)
down_after = win.after(100, volumeDownPress)
def volumeDownRelease(e=None):
global down_after
win.after_cancel(down_after)
volumeSlider = Scale(win, from_=0, to=100, orient=HORIZONTAL)
volumeSlider.pack()
volumeDownButton = Button(win, text = "-", height = 1, width = 1)
volumeDownButton.pack(side = BOTTOM)
volumeDownButton.bind("<Button-1>", volumeDownPress)
volumeDownButton.bind("<ButtonRelease-1>", volumeDownRelease)
volumeUpButton = Button(win, text = "+", height = 1, width = 1)
volumeUpButton.pack(side = BOTTOM)
volumeUpButton.bind("<Button-1>", volumeUpPress)
volumeUpButton.bind("<ButtonRelease-1>", volumeUpRelease)
win.mainloop()
Things to note:
I didn't use the Button's command since that doesn't give you the flexibility of knowing when the Button is released. Instead I made two binds, one for when the Button is pressed, one for when it is released.
I use the after method to schedule a new call to the same function after a set number of milliseconds, I save a reference to this scheduled function in a global variabel, to be able to use it again in the release function to cancel it with after_cancel
.bind calls functions with an event object, while after calls a function without arguments, because both call the same function, I made it so that the function can be called both with and without argument (e=None)
An alternative to fhdrsdg's answer that also uses after would be to measure the state value of the Button and detect whether it is currently active, to do this we bind a function to the Button which checks the state and then increments a value if the state is active before using after to call the function again after a short delay:
from tkinter import *
class App():
def __init__(self, root):
self.root = root
self.button = Button(self.root, text="Increment")
self.value = 0
self.button.pack()
self.button.bind("<Button-1>", lambda x: self.root.after(10, self.increment))
def increment(self, *args):
if self.button["state"] == "active":
self.value += 1
print(self.value)
self.root.after(10, self.increment)
root = Tk()
App(root)
root.mainloop()

How do I properly update the countdown timer in one second intervals in the main window? [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()

Categories

Resources