Python tKinter: How to pause countdown timer - python

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

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.

Related

Reaction Time Game counting rong (Python 3.10)

I'm making a reaction time game. description: Tkinter GUI, after random time a box with random color, size, font, place appears. openpyxl to save the game data if player wants to, threading for counting the seconds used, DateTime to put inside excel sheet for game data, random to choose place, size, font, color, text in box.
the intended game is that you get a box that appears after some time, you click, that box as fast as you can, it loops over that 3 times, when 3 times is finished you get a stats GUI with the time each of the 3 times, average time, save btn, play again btn.
error: the error is that the game counts wrong, if you try the game you will see that when the stats GUI come up all the time will be incremented by 0.01, example:
time 1: 1.15
time 2: 1.16
time 3: 1.17
if someone could point out the error that would be amazing.
code:
from tkinter import *
from tkinter import messagebox
from time import sleep
import openpyxl
import datetime
import random
import threading
def run_game():
root = Tk()
root.title("Reaction Time Game")
root.iconbitmap("C:/Users/Axelr/PycharmProjects/PC01/main/Self built/Reaction Time Game/icon.ico")
root.geometry("1400x1000")
root.configure(bg="orange")
workbook = openpyxl.load_workbook(filename="time_data.xlsx")
sheet = workbook.active
time_used_list = []
first_run = True
def clicked3():
global keep_counting
keep_counting = False
root.destroy()
root2 = Tk()
root2.title("Reaction Time Game Completed")
root2.iconbitmap("C:/Users/Axelr/PycharmProjects/PC01/main/Self built/Reaction Time Game/icon.ico")
root2.geometry("700x570")
root2.configure(bg="lightblue")
def save():
current_datetime = datetime.datetime.now()
current_datetime = current_datetime.strftime("%Y-%m-%d | %H:%M")
place = sheet.max_row + 1
sheet[f"A{place}"] = round(time_used_list[0], 2)
sheet[f"B{place}"] = round(time_used_list[1], 2)
sheet[f"C{place}"] = round(time_used_list[2], 2)
average = sum(time_used_list) / len(time_used_list)
sheet[f"D{place}"] = round(average, 2)
sheet[f"E{place}"] = current_datetime
workbook.save(filename="time_data.xlsx")
print("Saved time data")
def run_another():
root2.destroy()
run_game()
print("list of times:")
for time in time_used_list:
print(time)
title_label = Label(root2, text="Results", font=("Arial", 20, "bold"), bg="orange")
title_label.pack(padx=20, pady=20)
time1_label = Label(root2, text=f"Time used 1: {round(time_used_list[0], 2)} seconds", font=("Arial", 20), bg="orange")
time1_label.pack(padx=20, pady=20)
time2_label = Label(root2, text=f"Time used 2: {round(time_used_list[1], 2)} seconds", font=("Arial", 20), bg="orange")
time2_label.pack(padx=20, pady=20)
time3_label = Label(root2, text=f"Time used 3: {round(time_used_list[2], 2)} seconds", font=("Arial", 20), bg="orange")
time3_label.pack(padx=20, pady=20)
average_time = sum(time_used_list) / len(time_used_list)
average_label = Label(root2, text=f"Average Time Used: {round(average_time, 2)} seconds", font=("Arial", 20), bg="orange")
average_label.pack(padx=20, pady=20)
save_btn = Button(root2, text="Save", font=("Arial", 20), command=save, bg="orange")
save_btn.pack(padx=20, pady=20)
play_btn = Button(root2, text="Play Again", font=("Arial", 20), bg="orange", command=run_another)
play_btn.pack(padx=20, pady=20)
root2.mainloop()
def clicked():
global keep_counting, times_clicked, first_run
keep_counting = False
if times_clicked == 2:
clicked3()
else:
times_clicked += 1
btn.destroy()
print(first_run)
print(times_clicked)
first_run = False
start(first_run)
def time_thread():
global time_used
time_used = 0.00
while keep_counting:
sleep(0.01)
time_used += 0.01
time_used_list.append(time_used)
def start(first):
global keep_counting, times_clicked, first_run
if first:
times_clicked = 0
first_run = False
color_list = ["red", "green", "yellow", "cyan", "white", "blue", "magenta"]
random_color = random.choice(color_list)
random_x = random.randint(100, 600)
print(f"random_x = {random_x}")
random_y = random.randint(100, 600)
print(f"random_y = {random_y}")
random_width = random.randint(5, 15)
random_height = random.randint(5, 20)
print(f"random_width = {random_width}, random_height = {random_height}")
font_list = ["Helvetica", "Garamond", "Frutiger", "Bodoni", "Times", "Futura"]
random_font = random.choice(font_list)
random_font_size = random.randint(10, 18)
print(f"Font = {random_font}, font_size = {random_font_size}")
text_list = ["!", "?", "/", "+", "=", "<", ">", "%", "&", "(", ")", "-", "|", ";", ":", "[", "]", "{", "}", "^",
"#", "#"]
random_text = random.choice(text_list)
print(f"Random text = {random_text}")
random_time = random.randint(3, 5)
print(f"Random time = {random_time}")
thread = threading.Thread(target=time_thread)
keep_counting = True
def btn_create():
global btn
btn = Button(root, text=random_text, font=(random_font, random_font_size), bg=random_color, width=random_width, command=clicked)
btn.place(x=random_x, y=random_y)
thread.start()
time_wait = random_time * 1000
root.after(time_wait, btn_create)
start(first_run)
root.mainloop()
run_game()
please help me.
Your time_thread assumes that a sleep of 0.01 seconds always takes 0.01 seconds. That is patently false. Sleep merely guarantees you will wait for AT LEAST 0.01 seconds. On Windows, for example, the scheduler interval is about 0.016 seconds, so you can never sleep less than that. You can fix that by changing your timing thread to just snapshot the time before and after.
def time_thread():
start = time.time()
while keep_counting:
time.sleep(0.01)
time_used_list.append(time.time()-start)
HOWEVER, even that has a huge flaw. The problem here is that, because of Python's interpreter lock, it's quite possible for keep_counting to be set False and then True again before the time_thread gets a chance to run, in which case it will just keep running. You end up with all three time_threads counting at once.
You don't need that loop. Just capture the starttime when you display the button, and capture the end time when it is clicked. Eliminate keep_counting and the time thread altogether. This eliminated the need for importing threading at all.
So:
def run_game():
root = Tk()
root.title("Reaction Time Game")
root.geometry("1400x1000")
root.configure(bg="orange")
starttime = 0
time_used_list = []
first_run = True
def clicked3():
root.destroy()
root2 = Tk()
...
def clicked():
global times_clicked, first_run
time_used_list.append(time.time()-starttime)
if times_clicked == 2:
clicked3()
...
random_time = random.randint(3, 5)
print(f"Random time = {random_time}")
def btn_create():
global btn
btn = Button(root, text=random_text, font=(random_font, random_font_size), bg=random_color, width=random_width, command=clicked)
btn.place(x=random_x, y=random_y)
starttime = time.time()
time_wait = random_time * 1000
root.after(time_wait, btn_create)
As a rule, don't count elapsed time using a thread. Just grab a start time and an end time and subtract.

Why is the execution of my code (the program) happening faster, every time I hit the start button?

I just recently made a pomodoro clock implementation on python and I was facing some trouble with its execution but I came up with a solution for it but I observed a discrepancy in its execution.
So my whole code and implementation looks something like this (my code is not clean or pretty; sorry about that, I am one of those people who uses a mixture of tabs and spaces):
from tkinter import *
from time import *
from threading import Thread
# ---------------------------- CONSTANTS ------------------------------- #
PINK = "#e2979c"
RED = "#e7305b"
GREEN = "#9bdeac"
YELLOW = "#f7f5dd"
FONT_NAME = "Courier"
WORK_MIN = 25
SHORT_BREAK_MIN = 5*60000
LONG_BREAK_MIN = 20*60000
class Reset:
def __init__(self):
self.click = False
self.activation = False
def button_command(self):
self.click = True
def timer_command(self):
return self.click
# ---------------------------- TIMER RESET ------------------------------- #
timer_reset = Reset()
reset = timer_reset.click
# ---------------------------- TIMER MECHANISM ------------------------------- #
def timer_mechanism():
minute=0
if counter[0]<60000:
if counter[0] % 1000 == 0 and counter[0]/1000<10:
canvas.itemconfig(clock_counter, text=(f"00:0{int(counter[0]/1000)}"))
canvas.update_idletasks()
elif counter[0]% 1000==0 and counter[0]/1000>=10:
canvas.itemconfig(clock_counter, text=(f"00:{int(counter[0] / 1000)}"))
canvas.update_idletasks()
elif counter[0]>=60000:
if counter[0]%1000==0:
transition=int(counter[0]/1000)
while transition>=60:
minute+=1
transition-=60
second=transition
if second<10 and minute<10:
canvas.itemconfig(clock_counter,text=(f"0{minute}:0{second}"))
canvas.update_idletasks()
elif second>=10 and minute<10:
canvas.itemconfig(clock_counter,text=(f"0{minute}:{second}"))
canvas.update_idletasks()
elif minute>=10 and second<10:
canvas.itemconfig(clock_counter, text=(f"{minute}:0{second}"))
canvas.update_idletasks()
else:
canvas.itemconfig(clock_counter,text=(f"{minute}:{second}"))
canvas.update_idletasks()
# -----------------------------New countdown mechanism----------------------------#
i = [4]
def start_countdown():
if not timer_reset.click and i[0] > 0:
# keep listening to the function that receives from the button while doing an after for the 25 min and 10 min respectively.3
timer.config(text="Work", fg=GREEN)
window.update_idletasks()
# the solution to this problem is to break up the after process instead of doing it all at the same time make use of small increments so that this after could finish withing a second or two and then go to another function that contains all the timer_reset.click values update and come everytime until we reach a limit somewhere probably an array value that will be updated until we get to the 25 minute mark so this insures as the signal from the button will be accepted.
execute("Work")
# start=time()
# end=time()
# while end-start<5:
# end=time()
# timer_reset.click=timer_reset.timer_command()
else:
window.destroy()
timer_reset.click = False
i[0] = 4
beginning()
def rest():
global start_time
start_time=0
timer_reset.activation = True
if not timer_reset.click:
global restloop
restloop = True
global workloop
workloop = False
timer.config(text="Break", fg=PINK)
if i[0] == 4:
global frame
frame = Frame(window, width=20, height=20)
frame.grid(row=3, column=1)
tick = Label(frame, text="✔", font=(FONT_NAME, 12, "bold"), bg=YELLOW, fg=GREEN)
tick.pack(side=LEFT)
window.update_idletasks()
if i[0] > 0:
execute("Break")
if timer_reset.click:
timer_reset.click = False
window.destroy()
beginning()
else:
window.destroy()
beginning()
else:
window.destroy()
i[0] = 4
timer_reset.click = False
beginning()
i[0] -= 1
counter=[0]
def execute(identifier):
if identifier=="Work":
window.after(1,mirror,LONG_BREAK_MIN)
if identifier=="Break":
window.after(1,mirror,SHORT_BREAK_MIN)
def mirror(value):
if timer_reset.click:
window.destroy()
i[0]=4
timer_reset.click=False
counter[0]=0
beginning()
elif counter[0]<value:
counter[0]+=1
timer_mechanism()
if value==LONG_BREAK_MIN:
execute("Work")
if value==SHORT_BREAK_MIN:
execute("Break")
elif value==LONG_BREAK_MIN:
counter[0]=0
window.deiconify()
window.attributes("-topmost",1)
window.after(300,window.attributes,"-topmost",0)
window.update()
rest()
elif value==SHORT_BREAK_MIN:
counter[0]=0
window.deiconify()
window.attributes("-topmost",1)
window.after(300,window.attributes,"-topmost",0)
window.update()
start_countdown()
# ---------------------------- UI SETUP ------------------------------- #
def beginning():
global window
window = Tk(className="Pomodoro")
window.title("Pomodoro")
window.config(pady=50, padx=100, bg=YELLOW)
window.wm_state("normal")
global timer
timer = Label(text="Timer", font=(FONT_NAME, 50, "bold"), bg=YELLOW, foreground=GREEN)
timer.grid(row=0, column=1)
global canvas
canvas = Canvas(width=200, height=223, bg=YELLOW, highlightthickness=0)
tomato = PhotoImage(file="tomato.png")
canvas.create_image(100, 100, image=tomato)
global clock_counter
clock_counter=canvas.create_text(100, 120, text="00:00", fill="white", font=(FONT_NAME, 35, "bold"))
canvas.grid(row=1, column=1)
start = Button(text="Start", highlightthickness=0, borderwidth=0, command=start_countdown)
start.grid(row=2, column=0)
end = Button(text="Reset", highlightthickness=0, borderwidth=0, command=timer_reset.button_command)
end.grid(row=2, column=2)
window.mainloop()
beginning()
And the UI part of my code looks something like this:
So the discrepnancy I was talking about was that after starting the program and the countdown is happening if by mistake or whatever reason the user presses the Start button again the countdown starts to happen faster and if you keep pressing the start button without hitting the reset button it starts counting down even faster and faster.
Now I think I understand why this might be but this is like a sliver of what the solution might be, I might be completely wrong for all I know but is it because of the after method since every time the user hits the button he is starting another thread of the after method process and this kind of creates a sense/illusion of multiprocessing(parallelism)? I really don't know, to be honest. I might be stretching too far, but please any type of explanation would really be helpful even if it is a jab at my answer. I am very genuinely curious as to why this is happening.
Any help/suggestion will be really helpful.

Assign the return of a function to a variable with window.after

I'm working on day 89 of 100 Days of Code: Python. I need to build a Tkinter app that constantly monitors a text entry and will delete the code if it detects that nothing's been typed for 10 seconds. I've tried to do so using this code:
def get_wordcount(self):
start_num_char = len(self.entry_box.get(1.0, "end-1c"))
new_num_char = self.after(5000, self.get_wordcount)
if new_num_char <= start_num_char:
return True
else:
return False
I have also tried:
def get_wordcount(self):
start_num_char = len(self.entry_box.get(1.0, "end-1c"))
new_num_char = self.after(5000, len(self.entry_box.get(1.0, "end-1c")))
if new_num_char <= start_num_char:
return True
else:
return False
The problem is that new_num_char equals "after#0", "after#1", "after#2", etc. instead of equaling the new character count. How can I grab the new word count every five seconds? If I do:
def get_wordcount(self):
start_num_char = len(self.entry_box.get(1.0, "end-1c"))
self.after(5000)
new_num_char = len(self.entry_box.get(1.0, "end-1c")
if new_num_char <= start_num_char:
return True
else:
return False
This just freezes the whole window and I can't type into the entry box until the five seconds are up. I'd really appreciate some help on this; I've been trying to figure it out for a few days now. Full code below:
from tkinter import *
from tkinter.scrolledtext import ScrolledText
class App(Tk):
def __init__(self):
super().__init__()
self.title("Focus Writer")
self.background_color = "#EAF6F6"
self.config(padx=20, pady=20, bg=self.background_color)
self.font = "Arial"
self.start = False
self.time_left = 10
self.title_label = Label(text="Focus Writer",
bg=self.background_color,
font=(self.font, 26))
self.title_label.grid(row=0, column=0, columnspan=2)
self.explain = Label(text="Welcome to the Focus Writer app. You need to continuously input content in order to "
"keep the app from erasing your data. If the app detects that you haven't written "
"anything for more than 10 seconds, it will wipe everything, and you will need to "
"start over. \n\nPress the start button when you are ready to begin.\n",
bg=self.background_color,
font=(self.font, 18),
wraplength=850,
justify="left",)
self.explain.grid(row=1, column=0, columnspan=2)
self.start_img = PhotoImage(file="play-buttton.png")
self.start_button = Button(image=self.start_img,
height=50,
command=self.check_writing)
self.start_button.grid(row=2, column=0)
self.time_left_label = Label(text=self.time_left,
bg=self.background_color,
font=(self.font, 36))
self.time_left_label.grid(row=2, column=1)
self.entry_box = ScrolledText(width=80,
height=20,
wrap=WORD,
font=(self.font, 14))
self.entry_box.grid(row=3, column=0, columnspan=2)
def countdown(self):
if self.time_left > 0:
self.time_left -= 1
self.time_left_label.configure(text=self.time_left)
self.after(1000, self.countdown)
else:
if self.get_wordcount():
self.entry_box.delete(1.0, "end-1c")
else:
self.time_left = 0
return False
def get_wordcount(self):
start_num_char = len(self.entry_box.get(1.0, "end-1c"))
new_num_char = self.after(5000, len(self.entry_box.get(1.0, "end-1c")))
if new_num_char <= start_num_char:
return True
else:
return False
def check_writing(self):
self.start = True
self.start_button["state"] = DISABLED
self.entry_box.focus()
self.get_wordcount()
app = App()
app.mainloop()
I would solve this another way. Create a function cancels any previous attempt to clear the function and then schedules this function to be called in 10 seconds. Then, create a binding that calls this button on every key release. The entire mechanism takes only a half dozen lines of code.
Here's an example:
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.after_id = None
text = tk.Text(self)
text.pack(fill="both", expand=True)
text.bind("<Any-KeyRelease>", self.schedule_clear_text)
def schedule_clear_text(self, event):
if self.after_id is not None:
self.after_cancel(self.after_id)
self.after_id = self.after(10000, event.widget.delete, "1.0", "end")
root = App()
root.mainloop()
First I would split in two functions
First which set self.start_num_char and run self.get_wordcount() after some time
Second (self.get_wordcount) which get new_num_char, compare with self.start_num_char - but it doesn't use return - it directly resets timer - self.time_left = 10 - when text in entry is longer (or shorter). And finally it keep new_num_char in self.start_num_char and runs again after short time
def start_wordcount(self):
self.start_num_char = len(self.entry_box.get(1.0, "end-1c"))
self.after(50, self.get_wordcount)
def get_wordcount(self):
new_num_char = len(self.entry_box.get(1.0, "end-1c"))
if new_num_char != self.start_num_char: # longer or shorter
self.time_left = 10
self.time_left_label.configure(text=self.time_left)
self.start_num_char = new_num_char
self.after(50, self.get_wordcount)
And at the same time counter display new time and clean entry when self.time_left is 0
def countdown(self):
if self.time_left > 0:
self.time_left -= 1
self.time_left_label.configure(text=self.time_left)
self.after(1000, self.countdown)
else:
self.entry_box.delete(1.0, "end-1c")
self.start_num_char = 0
This count 10 seconds only when you don't type.
But it has small problem - because counter and get_wordcount run separatelly so sometimes counter changes time to 9 when get_wordcount is reseting variable and it can display 9 but it shouldn't. Maybe it should use real time and it should keep time when last key was pressed and use this value to calculate counter.
from tkinter import *
from tkinter.scrolledtext import ScrolledText
class App(Tk):
def __init__(self):
super().__init__()
self.title("Focus Writer")
self.background_color = "#EAF6F6"
self.config(padx=20, pady=20, bg=self.background_color)
self.font = "Arial"
self.start = False
self.time_left = 10
self.title_label = Label(text="Focus Writer",
bg=self.background_color,
font=(self.font, 26))
self.title_label.grid(row=0, column=0, columnspan=2)
self.explain = Label(text="Welcome to the Focus Writer app. You need to continuously input content in order to "
"keep the app from erasing your data. If the app detects that you haven't written "
"anything for more than 10 seconds, it will wipe everything, and you will need to "
"start over. \n\nPress the start button when you are ready to begin.\n",
bg=self.background_color,
font=(self.font, 18),
wraplength=850,
justify="left",)
self.explain.grid(row=1, column=0, columnspan=2)
self.start_button = Button(text='Start',
command=self.check_writing)
self.start_button.grid(row=2, column=0)
self.time_left_label = Label(text=self.time_left,
bg=self.background_color,
font=(self.font, 36))
self.time_left_label.grid(row=2, column=1)
self.entry_box = ScrolledText(width=80,
height=20,
wrap=WORD,
font=(self.font, 14))
self.entry_box.grid(row=3, column=0, columnspan=2)
def countdown(self):
if self.time_left > 0:
self.time_left -= 1
self.time_left_label.configure(text=self.time_left)
self.after(1000, self.countdown)
else:
self.entry_box.delete(1.0, "end-1c")
self.start_num_char = 0
def start_wordcount(self):
self.start_num_char = len(self.entry_box.get(1.0, "end-1c"))
self.after(50, self.get_wordcount)
def get_wordcount(self):
new_num_char = len(self.entry_box.get(1.0, "end-1c"))
if new_num_char != self.start_num_char: # longer or shorter
self.time_left = 10
self.time_left_label.configure(text=self.time_left)
self.start_num_char = new_num_char
self.after(50, self.get_wordcount)
def check_writing(self):
self.start = True
self.start_button["state"] = DISABLED
self.entry_box.focus()
self.start_wordcount()
self.countdown()
app = App()
app.mainloop()
EDIT:
You may try to use self.entry_box.bind('<<Modified>>', function) to execute function(event) when text was changed. But on my Linux it runs only on first change.
EDIT:
I added time.time() to check time betwin changes and now it better display counter.
def countdown(self):
if self.time_left > 0:
current_time = time.time()
if current_time >= self.change_time + 1:
self.time_left -= 1
self.time_left_label.configure(text=self.time_left)
self.after(1000, self.countdown)
else:
self.entry_box.delete(1.0, "end-1c")
self.start_num_char = 0
def start_wordcount(self):
self.num_char = len(self.entry_box.get(1.0, "end-1c"))
self.change_time = time.time()
self.after(50, self.get_wordcount)
def get_wordcount(self):
new_num_char = len(self.entry_box.get(1.0, "end-1c"))
if new_num_char != self.num_char: # longer or shorter
self.time_left = 10
self.change_time = time.time()
self.time_left_label.configure(text=self.time_left)
self.num_char = new_num_char
self.after(50, self.get_wordcount)

How do I get this timer to count down properly?

I am trying to make a simple gui where you can press a button to start a a timer, and see it count down, similar to https://web.5217.app/, But I cannot get the timer to display in the gui, any help would be appreciated.
Also this is my first question so I may have done something wrong.
from tkinter import Tk, Button, DISABLED, Label, ACTIVE
import time
#main window configuration
root = Tk()
root.title ("PyDoro")
root.geometry ("400x400")
root.configure(bg = "#383838")
#colours for the text
Colour1 = "#c4c4c4"
Colour2 = "#292828"
def Date(): #Defines the date for displaying under the clock
day = time.strftime("%d")
month = time.strftime("%b") # %B can be used for full month
year = time.strftime("%Y")
Calendar.config (text= day + " " + month + " " + year)
def clock(): # for creating the clock
tizo = time.strftime("%X") #Find the time for your locale
Time.config (text = tizo)
Time.after (1000, clock)
Date() #calling the Date because it makes sense to do it here
def Stop():
print ("nothing")
def Start():
time_left = (50)
Stop.config (state = ACTIVE)
timer = Label (root, text = time_left)
timer.pack()
for i in range (50):
timer.config (text = time_left)
Start.after (1000) # this waits for 1 minute (60000 miliseconds)
#print (i) # This is just for debugging
time_left = time_left - 1
print (time_left)
Start = Button (root, text = "Start!", fg = Colour1, bg = Colour2, padx = 40, command = Start)
Stop = Button (root, text = "stop", fg = Colour1, bg = Colour2, state = DISABLED)
Time = Label (root, text="", font = ("Helvetica", 50), fg = Colour1, bg = "#383838")
Time.pack (pady = 5)
Calendar = Label (root, font = ("Helvetica", 12), fg = Colour1, bg = "#383838")
Calendar.pack (pady = 5)
Start.pack (pady = 10)
Stop.pack (pady = 10)
clock()
root.mainloop() #Runs the program
Replace your Start() function with the following code:
def Start():
time_left = (50)
Stop.config (state = ACTIVE)
timer = Label (root, text = time_left)
timer.pack()
def update(time_left):
timer['text'] = time_left
if time_left > 0:
root.after(1000, update, time_left-1)
update(time_left)
After you create your label, the program calls a function called update, which sets the text of the timer label to time_left. It will then call root.after if time_left is greater than 0, and it passes time_left -1 back into the update function. This will make the timer countdown until it reaches 0.
The reason the timer Label isn't being shown is because the display is never given a chance to update. To fix that, try using the Start() function shown below which calls the universal widget method update_idletasks() to update it after it's been changed.
def Start():
time_left = 50
Stop.config(state=ACTIVE)
timer = Label(root, text=time_left)
timer.pack()
for i in range(50):
timer.config(text=time_left)
time_left -= 1
root.update_idletasks() # Update display.
root.after(1000) # Pause for 1000 milliseconds (1 second).

How to make timer/program open only after pressing key instead of immediately?

I need to make this clock open only after pressing a key, lets say "t". Now it opens immediately after running it.
import tkinter as tk
def update_timeText():
if (state):
global timer
timer[2] += 1
if (timer[2] >= 100):
timer[2] = 0
timer[1] += 1
if (timer[1] >= 60):
timer[0] += 1
timer[1] = 0
timeString = pattern.format(timer[0], timer[1], timer[2])
timeText.configure(text=timeString)
root.after(10, update_timeText)
def start():
global state
state=True
state = False
root = tk.Tk()
root.wm_title('Simple Kitchen Timer Example')
timer = [0, 0, 0]
pattern = '{0:02d}:{1:02d}:{2:02d}'
timeText = tk.Label(root, text="00:00:00", font=("Helvetica", 50))
timeText.pack()
startButton = tk.Button(root, text='Start', command=start)
startButton.pack()
update_timeText()
root.mainloop()
It is in another program so as I have my graphics window I will press "t" and the clock will open.
Keyboard is a python module that can detect keystrokes. Install it by doing this command.
pip install keyboard
Now you can do this.
while True:
try:
if keyboard.is_pressed('t'):
state = True
elif(state != True):
pass
except:
state = False
break #a key other than t the loop will break
I would recommend you to organize the code little bit, like class structure. One possible implementation would be like that:
import tkinter as tk
TIMER = [0, 0, 0]
PATTERN = '{0:02d}:{1:02d}:{2:02d}'
class Timer:
def __init__(self, master):
#I init some variables
self.master = master
self.state = False
self.startButton = tk.Button(root, text='Start', command=lambda: self.start())
self.startButton.pack()
self.timeText = tk.Label(root, text="00:00:00", font=("Helvetica", 50))
self.timeText.pack()
def start(self):
self.state = True
self.update_timeText()
def update_timeText(self):
if (self.state):
global TIMER
TIMER[2] += 1
if (TIMER[2] >= 100):
TIMER[2] = 0
TIMER[1] += 1
if (TIMER[1] >= 60):
TIMER[0] += 1
TIMER[1] = 0
timeString = PATTERN.format(TIMER[0], TIMER[1], TIMER[2])
self.timeText.configure(text=timeString)
self.master.after(10, self.update_timeText)
if __name__ == '__main__':
root = tk.Tk()
root.geometry("900x600")
root.title("Simple Kitchen Timer Example")
graph_class_object = Timer(master=root)
root.mainloop()
So clock will start when you click to button. If you want to start the clock by pressing "t" in keyboard, you need to bind that key to your function.
You can also add functionality if you want to stop the clock when you click to the button one more time.
EDIT:
if you also want to start to display the clock by clicking the button, you can move the code for initializing the label in to start function.
def start(self):
self.state = True
self.timeText = tk.Label(root, text="00:00:00", font=("Helvetica", 50))
self.timeText.pack()
self.update_timeText()

Categories

Resources