Reputation: 143
I made a countdown timer that starts whenever I press on the space key on my keyboard, but the problem is that I can't do anything on the program until the timer ends. I want make it pause when the space key is pressed a second time.
The countdown timer that I made works in a while loop that ends when the timer reach 0, so the program waits until the loop ends before doing anything else, even if I want to stop the timer I can't do it while it's running.
Here's the code
from tkinter import *
from tkinter import ttk
import tkinter as tk
from PIL import ImageTk, Image
def StartTimer():
if (root.turn % 2) == 0: #Turn to white
root.number = '1'
root.color = 'white'
else: #Turn to black
root.number = '2'
root.color = 'black'
doTimer()
def doTimer():
root.time = root.minute *60 + root.second
root.turn = root.turn+1
number = root.number
root.rndsquare1.configure(image=root.green)
root.timer1.configure(bg='#1C953D')
root.white.configure(bg='#1C953D')
r=0
while r < root.time:
root.update_idletasks()
root.after(1000)
root.second = root.second - 1
if root.second == -1:
root.minute = root.minute -1
root.second = 59
root.time1 = ''
if len(str(root.minute)) == 1:
root.time1 = '0' + str(root.minute)
else:
root.time1 = str(root.minute)
if len(str(root.second)) == 1:
root.time1 = root.time1 + ':' + '0' + str(root.second)
else:
root.time1 = root.time1 + ':' + str(root.second)
root.timer1.configure(text=root.time1)
r=r+1
root.timer1.configure(bg='#454545')
root.white.configure(bg='#454545')
root.rndsquare1.configure(image=root.grey)
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"D:\Users\Jean Paul\OneDrive\Programming\Programs\Prog 6 - Chess Clock\bg square grey.png"))
self.green = ImageTk.PhotoImage(Image.open(r"D:\Users\Jean Paul\OneDrive\Programming\Programs\Prog 6 - Chess Clock\bg square green.png"))
self.turn=0
self.rndsquare1 = Label(self, image=self.grey, borderwidth=0)
self.rndsquare1.place(x=65, y=120)
self.rndsquare2 = Label(self, image=self.grey, borderwidth=0)
self.rndsquare2.place(x=809, y=120)
self.bind('<space>',lambda event:StartTimer())
self.createTimers()
def createTimers(self):
self.minute = 1
self.second = 5
self.time1 = ''
if len(str(self.minute)) == 1:
self.time1 = '0' + str(self.minute)
else:
self.time1 = str(self.minute)
if len(str(self.second)) == 1:
self.time1 = self.time1 + ':' + '0' + str(self.second)
else:
self.time1 = self.time1 + ':' + str(self.second)
self.time2 = ''
if len(str(self.minute)) == 1:
self.time2 = '0' + str(self.minute)
else:
self.time2 = str(self.minute)
if len(str(self.second)) == 1:
self.time2 = self.time2 + ':' + '0' + str(self.second)
else:
self.time2 = self.time2 + ':' + str(self.second)
self.timer1 = Label(self, text=self.time1, bg='#454545', fg='white', font ="Gadugi 40 bold")
self.timer1.place(x=330, y=420)
self.timer2 = Label(self, text=self.time2, bg='#454545', fg='white', font ="Gadugi 40 bold")
self.timer2.place(x=1080, y=420)
self.white = Label(self, text='White', bg='#454545', fg='white', font ="Gadugi 40 bold")
self.white.place(x=325, y=160)
self.black = Label(self, text='Black', bg='#454545', fg='white', font ="Gadugi 40 bold")
self.black.place(x=1075, y=160)
root=root()
root.mainloop()
D:\Users\Jean Paul\OneDrive\Programming\Programs\Prog 6 - Chess Clock\bg square grey.png
D:\Users\Jean Paul\OneDrive\Programming\Programs\Prog 6 - Chess Clock\bg square green.png
Upvotes: 2
Views: 524
Reputation: 51653
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
after the first CTRL+q
you'll get the results - a second time CTRL+q
closes your window:
This can be better structured regarding UI/logic stuff - but it works as proof of concept.
Upvotes: 3