Making a Countdown Timer with Python and Tkinter

Attempting to make countdown timer with Tkinter

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()

Making a countdown timer with Python and Tkinter?

Tkinter already has an infinite loop running (the event loop), and a way to schedule things to run after a period of time has elapsed (using after). You can take advantage of this by writing a function that calls itself once a second to update the display. You can use a class variable to keep track of the remaining time.

import Tkinter as tk

class ExampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.label = tk.Label(self, text="", width=10)
self.label.pack()
self.remaining = 0
self.countdown(10)

def countdown(self, remaining = None):
if remaining is not None:
self.remaining = remaining

if self.remaining <= 0:
self.label.configure(text="time's up!")
else:
self.label.configure(text="%d" % self.remaining)
self.remaining = self.remaining - 1
self.after(1000, self.countdown)

if __name__ == "__main__":
app = ExampleApp()
app.mainloop()

Display a countdown timer in TKinter while making the code BLOCKING but do not freeze the GUI

Currently, your question doesn't make a lot of sense to me. From what I understand you want your job() function to be called after the countdown.

Using thread for this is unnecessary. You can just use after and once the timer reaches 0 call the job() function.

Here is a minimal example

import tkinter as tk

def job():
status.config(text="starting job")

def countdown(time, msg='Counting down'):

time -= 1
status.config(text=f'{msg} ({time}sec)')

if time != 0:
root.after(1000, countdown, time)

else:
job() # if job is blocking then create a thread

root = tk.Tk()

status = tk.Label(root)
status.pack()

countdown(20)

root.mainloop()

Python tKinter: How to pause countdown timer

You can solve this by heavily refacturing your code. You can add 2 clocks to your widget, each clock tracks how much is spent on itself. The spacebar listener simply switches between which clock is currently in use. By also having a timed do_clock_logic every 200ms or so it checks if a current clock is set, if so if the time is up and if that is the case, switch over to the other clock. In any case it will trigger the clocks tick() method to update its internal states that also handle ui updates.

This way there is no "blocking" while loop and all timing stuff is handled by tk:

from tkinter import Tk, Label
import tkinter as tk
from PIL import ImageTk, Image
from datetime import datetime, timedelta

class clock():
"""A single clock that handles updating/timekeeping itself. It uses
the both class-level memebrs as active/inactive image and has
references provided to place the image and the timing text."""
active_img = None
deactive_img = None

@staticmethod
def format_time(delta, ms = False):
"""Returns a formatted strng for a timedelta instance,
optionally with milliseconds"""

return f"{delta.seconds//60:02}:{delta.seconds%60:02}" + (
f".{(delta.microseconds // 1000):04}" if ms else "")

def __init__(self, minutes, seconds, bg_lbl, text_lbl):
"""Set the clocks max duration providing 'minutes' and 'seconds'.
Provide tk-labels with a background image 'bg_lbl' and
'text_lbl' for the time display."""
self.max_duration = timedelta(seconds=seconds+minutes*60)
# UI
self.bg_lbl = bg_lbl,
self.text_lbl = text_lbl
# reset to inactive image and no text
self.bg_lbl[0].config(image = clock.deactive_img)
self.text_lbl.config(text = "")
# internal time keeping of total spent time1
self.total = timedelta() # 0 delta at start

def update_lbl(self, spent):
# update the image if needed
self.bg_lbl[0].config(image = clock.active_img if self.started is not None else clock.deactive_img)

# update labels - if not active - show with milliseconds
if self.started is not None:
self.text_lbl.config( text = clock.format_time(self.max_duration - spent))
else:
self.text_lbl.config(text = f"Total:\n{clock.format_time(self.total, True)}")

def start_clock(self):
# starts the clock
self.started = datetime.now()
self.update_lbl(timedelta())

def tick(self):
# ticks the clock - stops it if time has run out
if self.started is not None:
spent = datetime.now() - self.started
if spent > self.max_duration:
self._stop_clock(spent)
return False

self.update_lbl(spent)
return True
return None

def stop_clock(self):
# stop clock from the outside if <space> is hit
if self.started is not None:
spent = datetime.now() - self.started
self._stop_clock(spent)

def _stop_clock(self, spent):
# internal method that stops the clock, adds total & updates
spent = min(spent, self.max_duration) # fix it
self.total += spent
self.started = None
self.update_lbl(None)

class root(Tk):
def __init__(self):
super(root, self).__init__()

self.title("Chess Clock")
self.minsize(1539,600)
self.windowBG = '#313131'
self.state('zoomed')
self.configure(bg=self.windowBG)

self.CreateWindow()

def CreateWindow(self):
self.grey = ImageTk.PhotoImage(Image.open(r"grey.png"))
self.green = ImageTk.PhotoImage(Image.open(r"green.png"))

# used to determine player
self.turn = 0

# give the clock class the two images to switch
# if changing between active/inactive state
clock.deactive_img = self.grey
clock.active_img = self.green

# one clocks UI
self.white_bg = Label(self, image=self.grey, borderwidth=0)
self.white_bg.place(relx=.3, rely=.55, anchor="center")
self.white = Label(self, text='White', bg='#454545', fg='white', font ="Gadugi 40 bold")
self.white.place(relx=.3, rely=.2, anchor="center")
self.white_timer = Label(self.white_bg, text="", bg='#454545', fg='white', font ="Gadugi 40 bold")
self.white_timer.place(relx=.5, rely=.5, anchor="center")

# seconds clock UI
self.black_bg = Label(self, image=self.grey, borderwidth=0)
self.black_bg.place(relx=.7, rely=.55, anchor="center")
self.black = Label(self, text='Black', bg='#454545', fg='white', font ="Gadugi 40 bold")
self.black.place(relx=.7, rely=.2, anchor="center")
self.black_timer = Label(self.black_bg, text="", bg='#454545', fg='white', font ="Gadugi 40 bold")
self.black_timer.place(relx=.5, rely=.5, anchor="center")

# provide the background-label and the text label
# for time and create two clocks for the players
self.clock1 = clock(1, 5, self.white_bg, self.white_timer)
self.clock2 = clock(1,5, self.black_bg, self.black_timer)

# which clock is currently in use?
self.who_is_it = None
# handles switching to next players clock
self.bind('<space>', lambda _: self.next_player())
self.bind('<Control-Key-q>', lambda _: self.stop())
# check every 200ms if clocks need to be switched over
self.after(200, self.do_clock_logic)

def do_clock_logic(self):
# do nothing if no clock startet
# check if clock has run out, then switch to next players clock
if self.who_is_it is not None:
# tick() returns False if the player spent all his time
# tick() returns True if the player still has time
# tick() returns None if clock is not yet started
if self.who_is_it.tick() == False:
self.next_player()

# recheck clocks in 200ms
self.after(200, self.do_clock_logic)

def stop(self):
"""First Ctrl+q will stop clocks, second will quit."""
if self.who_is_it is not None:
self.who_is_it.stop_clock()
self.who_is_it = None
self.do_clock_logic = lambda _: None is None
else:
self.destroy()

def next_player(self):
if self.who_is_it is not None:
self.who_is_it.stop_clock()

self.turn += 1
# player 1 on "odd turns", player 2 on "even turns"
self.who_is_it = self.clock1 if self.turn % 2 else self.clock2
self.who_is_it.start_clock()

root=root()
root.mainloop()

to get

image of the clock while running

after the first CTRL+q you'll get the results - a second time CTRL+q closes your window:

image after first ctrl+q

This can be better structured regarding UI/logic stuff - but it works as proof of concept.

Python tkinter: Countdown Timer not accurate

I don't know if it's doing that because the code of the clock takes some extra time to execute

In this case you are right, the tempo of your timer is dependent on the runtime of your code. So making this script require more resources from your computer would also slow down the timer, and vice versa.

"Don't reinvent the wheel." - Programming proverb.

Use the time module to get a more accurate time in your timer. More specifically time.time() and format it to make it readable.

Edit:

Here's an example to show your problem.

import time

time_zone = 0 # UTC
while True:
current_time = time.time()

ms = (current_time * 1000) % 1000
s = (current_time % 60)
m = (current_time % (60*60))//60
h = (current_time % (60*60*24))//(60*60)

print(f"h:{int(h+time_zone)} - m:{int(m)} - s:{int(s)} - ms:{int(ms)}")
time.sleep(1)

In theory this script should print the time exactly every second. However a couple of milliseconds are added due to the code runtime.
Removing time.sleep(1) should get you pretty close to a live clock.

So if you want something close to a timer or stopwatch, you get a timestamp for the current epoch time (time.time()) update your timer about every x-amount of ticks per second to get the effect of counting ms, when the button is pressed again you get a new timestamp, and then you compare it to the first timestamp and subtract to get the exact time between each button press.


I've made a template for you to tinker a bit around with:

import tkinter as tk
import time

time_zone = 0 # UTC

# Convert epoch time to readable format
def readable_epoch(time_epoch):
ms = (time_epoch * 1000) % 1000
s = (time_epoch % 60)
m = (time_epoch % (60*60))//60
h = (time_epoch % (60*60*24))//(60*60)
return (h, m, s, ms)

class App(tk.Tk):
def __init__(self):
super().__init__()
self.time1 = 0
self.time2 = 0

self.geometry("300x200")
self.button1 = tk.Button(
self, text="Click me first", command=self.button1, bg="teal")
self.button1.pack(ipadx=10, ipady=10, expand=True, side=tk.LEFT)
self.button2 = tk.Button(
self, text="Click me second", command=self.button2, bg="pink")
self.button2.pack(ipadx=10, ipady=10, expand=True, side=tk.LEFT)

# Get current time
def button1(self):
self.current_time = time.time()
h, m, s, ms = readable_epoch(self.current_time)
print(f"h:{int(h+time_zone)}, m:{int(m)}, s:{int(s)}, ms:{int(ms)}")

# Elapsed time since button 1 was pressed:
def button2(self):
new_time = time.time()
new_time = new_time - self.current_time
h, m, s, ms = readable_epoch(new_time)
print(f"h:{int(h+time_zone)}, m:{int(m)}, s:{int(s)}, ms:{int(ms)}")

if __name__ == "__main__":
app = App()
app.mainloop()



Related Topics



Leave a reply



Submit