Python 3.6.
This is a class i made for tkinter, where the text of self.compteur changes every second once i press start :
motsCount = 0
temps_total = 3600
class ChronoAspi:
def __init__(self, master):
self.master = master
self.mainframe = tkinter.Frame(self.master, background ='#28aae1')
self.mainframe.pack(fill = tkinter.BOTH, expand= True)
self.timer_text = tkinter.StringVar()
self.timer_text.trace('w', self.build_timer)
self.time_left = tkinter.IntVar()
self.time_left.set(temps_total)
self.running = True
self.buildGrid()
self.build_buttons()
self.build_timer()
self.build_compteur()
self.update()
def buildGrid(self):
self.mainframe.columnconfigure(0, weight=1)
self.mainframe.rowconfigure(0, weight=1)
self.mainframe.rowconfigure(1, weight=1)
self.mainframe.rowconfigure(2, weight=0)
def build_buttons(self):
buttons_frame = tkinter.Frame(self.mainframe)
buttons_frame.grid(row=2, column=0, sticky='nsew', padx=10, pady=10)
buttons_frame.columnconfigure(0, weight=1)
buttons_frame.columnconfigure(1, weight=1)
self.start_button = tkinter.Button(buttons_frame, text='Start', command=self.start_timer )
self.stop_button = tkinter.Button(buttons_frame, text='Stop', command=self.stop_timer)
self.start_button.grid(row=0, column=0, sticky = 'ew')
self.stop_button.grid(row=0, column=1, sticky = 'ew')
self.stop_button.config(state=tkinter.DISABLED)
def build_timer(self, *args):
timer = tkinter.Label(self.mainframe, text=self.timer_text.get(), background = '#28aae1', fg='white', font=("Helvetica", 30))
timer.grid(row=1, column=0)
def build_compteur(self, *args):
self.compteur = tkinter.Label(self.mainframe, text='Aucun mot compté.', background = '#28aae1', fg='white', font=("Helvetica", 20))
self.compteur.grid(row=0, column=0)
def start_timer(self):
self.time_left.set(temps_total)
self.running = True
self.stop_button.config(state=tkinter.NORMAL)
self.start_button.config(state=tkinter.DISABLED)
def stop_timer(self):
self.running = False
self.stop_button.config(state=tkinter.DISABLED)
self.start_button.config(state=tkinter.NORMAL)
def heures_minutes_secondes(self, seconds):
return int(seconds/3600), int(seconds%3600/60), int(seconds%60)
def update(self):
global motsCount
time_left = self.time_left.get()
if self.running and time_left:
heure, minutes, seconds = self.heures_minutes_secondes(time_left)
self.timer_text.set('{:0>2}:{:0>2}:{:0>2}'.format(heure ,minutes, seconds) )
self.time_left.set(time_left-1)
motsCount += 1
else:
heure, minutes, seconds = self.heures_minutes_secondes(time_left)
self.timer_text.set( '{:0>2}:{:0>2}:{:0>2}'.format(heure ,minutes, seconds))
self.compteur['text'] = 'Compteur stoppé.'
self.stop_timer()
if motsCount > 0:
self.compteur['text'] = str(motsCount) + ' mots comptés.'
self.master.after(1000, self.update)
if __name__ == '__main__':
root = tkinter.Tk()
ChronoAspi(root)
root.mainloop()
As long as the chronometer is running, the text of self.compteurchanges every second. But when i hit the self.stop_button, self.running becomes False and the else part in the update() function is executed. So the chronometer stops but the self.compteur text doesn't change and I don't know why!
Sorry I can't comment but I think your if statement is gonna run after self.stopTimer returns and gonna change the text back
Related
I would like to achieve, when I click on the "Autopaging" button each page from the PDF gets displayed for 1s. Now when I click it, its loops through the list of pages, jumps to the last page, and doesn't display any other page.
To the auto_read function I placed print(pagenumber) to see when its looping through the pages.
from PIL import ImageTk, Image
from pdf2image import convert_from_path
import time
class SuReader:
def __init__(self, root):
self.root = root
self.next_but = Button(root, text='>>', command=lambda: self.next_page())
self.prev_but = Button(root, text='<<', command=self.prev_page)
self.automate_but = Button(root, text='Autopaging', command=self.auto_read)
self.next_but.grid(row=1, column=2)
self.prev_but.grid(row=1, column=0)
self.automate_but.grid(row=1, column=1)
self.display_pages()
def get_pages(self):
self.pages = convert_from_path('book.pdf', size=(700, 600))
self.photos = []
for i in range(len(self.pages)):
self.photos.append(ImageTk.PhotoImage(self.pages[i]))
def display_pages(self):
self.get_pages()
self.page_number = 0
self.content = Label(self.root, image=self.photos[self.page_number])
self.content.grid(column=0,row=0,sticky='NSEW', columnspan=3)
print(len(self.photos))
def next_page(self):
# self.page_number += 1
self.content.destroy()
self.content = Label(self.root, image=self.photos[self.page_number])
self.content.grid(column=0,row=0,sticky='NSEW', columnspan=3)
self.page_number += 1
def prev_page(self):
self.page_number -= 1
print(self.page_number)
self.content.destroy()
self.content = Label(self.root, image=self.photos[self.page_number])
self.content.grid(column=0,row=0,sticky='NSEW', columnspan=3)
def auto_read(self):
for i in range(len(self.photos)):
time.sleep(1)
print(self.page_number)
self.next_page()
root = Tk()
root.title('Book Reader')
program = SuReader(root)
root.mainloop()
Here I just pass it to the next_page function, but even if I define properly it doesn't work.
time.sleep is blocking mainloop and I believe you are also having a garbage collection problem. I rewrote your code and solved your problems. I solved other problems you were about to have, as well. The changes are commented in the script.
import tkinter as tk
from PIL import ImageTk, Image
from pdf2image import convert_from_path
class App(tk.Tk):
WIDTH = 700
HEIGHT = 640
TITLE = 'Book Reader'
def __init__(self, **kwargs):
tk.Tk.__init__(self, **kwargs)
#get_pages and display_pages are useless just do it all in the constructor
self.pages = convert_from_path('book.pdf', size=(700, 600))
self.photos = {}
for i in range(len(self.pages)):
#the image might get garbage collected if you don't do it this way
self.photos[f'photo_{i}'] = ImageTk.PhotoImage(self.pages[i])
self.auto = tk.IntVar()
self.was_auto = False
self.page_number = 0
self.content = tk.Label(self, image=self.photos[f'photo_{self.page_number}'])
self.content.grid(column=0, row=0, sticky='nsew', columnspan=3)
tk.Button(self, text='<<', command=self.prev_page).grid(row=1, column=0, sticky='w')
tk.Checkbutton(self, text='auto-paging', variable=self.auto, command=self.auto_page).grid(row=1, column=1)
tk.Button(self, text='>>', command=self.next_page).grid(row=1, column=2, sticky='e')
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(2, weight=1)
def next_page(self):
#stop a lingering `after` call from affecting anything if we unchecked auto-paging
if self.auto.get() == 0 and self.was_auto:
self.was_auto = False
return
self.page_number += 1
#never be out of range
self.page_number = self.page_number % len(self.pages)
#you don't have to remake the Label every time, just reassign it's image property
self.content['image'] = self.photos[f'photo_{self.page_number}']
#time.sleep(1) replacement ~ non-blocking
if self.auto.get() == 1:
self.after(1000, self.next_page)
def prev_page(self):
self.page_number -= 1
#never be out of range
if self.page_number < 0:
self.page_number += len(self.pages)
self.content['image'] = self.photos[f'photo_{self.page_number}']
def auto_page(self):
if self.auto.get() == 1:
#allows us to catch a lingering `after` call if auto-paging is unchecked
self.was_auto = True
self.next_page()
if __name__ == '__main__':
app = App()
app.title(App.TITLE)
app.geometry(f'{App.WIDTH}x{App.HEIGHT}')
app.resizable(width=False, height=False)
app.mainloop()
I am trying to create a tkinter GUI for a script which performs some task. The task is triggered by clicking a start button, and I would like to add a dynamic label showing that the task is "in progress" by displaying:
"Working." → "Working.." → "Working..."
I referred to this post and wrote the following script. Here I used a progress bar to represent my "task", and was expecting the status label to change (as stated above) WHILE the progress bar is updating.
import tkinter as tk
class UI:
def __init__(self):
self.root = tk.Tk()
self.root.title('Hello World')
self.prog_Label = tk.Label(self.root, text='Progress')
self.prog_Label.grid(row=0, column=0, sticky=tk.W, padx=20, pady=(10, 0))
self.prog_Bar = tk.ttk.Progressbar(self.root)
self.prog_Bar.configure(value=0, mode='determinate', orient='horizontal')
self.prog_Bar.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=20, pady=5)
self.exe_Btn = tk.Button(self.root, text='Start', padx=15, command=self.run, relief='groove')
self.exe_Btn.grid(row=2, column=0, padx=80, pady=(40, 20), sticky=tk.E)
self.prog_Label = tk.Label(self.root, text='Status:-')
self.prog_Label.grid(row=3, column=0, sticky=tk.W, padx=20, pady=10)
self.root.mainloop()
def run(self):
self.update_status('Working')
n = 0
self.prog_Bar.configure(value=n, maximum=100000, mode='determinate', orient='horizontal')
for i in range(100000):
n += 1
self.prog_Bar.configure(value=n)
self.prog_Bar.update_idletasks()
def update_status(self, status=None):
if status is not None:
current_status = 'Status: ' + status
else:
current_status = self.prog_Label['text']
if current_status.endswith('...'):
current_status = current_status.replace('...', '')
else:
current_status += '.'
self.prog_Label['text'] = current_status
self.prog_Label.update_idletasks()
self._status = self.root.after(1000, self.update_status)
if __name__ == '__main__':
ui = UI()
However, the program behaves in a way that, when the start button is clicked, although the status label changes from '-' to 'Working' immediately, it only starts to add the dots after the progress bar reaches the end.
Is there a way to modify it so as to achieve my purpose?
I changed your structure a little so your task is now in its own class, instead of sleeping you would perform the task there. I added a thread for the task as i assumed that it would need its own process, this stops the application freezing as it would block the main UI loop.
import threading
import time
import tkinter as tk
import tkinter.ttk as ttk
class Task:
def __init__(self):
self.percent_done = 0
threading.Thread(target = self.run).start()
def run(self):
while self.percent_done < 1:
self.percent_done += 0.1
# Do your task here
time.sleep(0.5)
self.percent_done = 1
class Application():
def __init__(self):
self.root = tk.Tk()
self.root.title("Window Title")
self.task = None
self.label_dots = 0
self.prog_bar = tk.ttk.Progressbar(self.root)
self.prog_bar.configure(value=0, maximum=100, mode='determinate', orient='horizontal')
self.prog_bar.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=20, pady=5)
self.run_btn = tk.Button(self.root, text='Start', padx=15, command=self.start_task, relief='groove')
self.run_btn.grid(row=2, column=0, padx=80, pady=(40, 20), sticky=tk.E)
self.prog_label = tk.Label(self.root, text='Status: -')
self.prog_label.grid(row=3, column=0, sticky=tk.W, padx=20, pady=10)
def start_task(self):
self.task = Task()
self.update_ui()
def update_ui(self):
# Percent is between 0 and 1
if 0 < self.task.percent_done < 1:
status = "Working"
self.label_dots += 1
self.label_dots = self.label_dots % 4
else:
status = "Finished"
self.label_dots = 0
self.prog_bar.configure(value=self.task.percent_done * 100)
label_text = "Status: - " + status + ("." * self.label_dots)
self.prog_label.config(text = label_text)
if status != "Finished":
self.root.after(1000, self.update_ui)
Application().root.mainloop()
I'm trying to create code to incrementally increase the voltage on a DC power supply over the span of an input duration. I've set up a GUI for doing this (it's my first try making a GUI, sorry if the code is weird), and everything works ... except that the GUI freezes while the code is executing so I can't stop the loop. I've looked into this for several hours and learned to use root.after instead of time.sleep, but it doesn't seem to have helped in the HeatLoop function. The GUI updates now, but only sporadically and there's still the "wait cursor" showing up when I mouse over the GUI. Is there some way to fix this?
I modified the code I'm using below so it should work on any computer without needing to be edited.
import datetime
import time
from tkinter import *
class GUIClass:
def __init__(self, root):
"""Initialize the GUI"""
self.root = root
self.percent = StringVar()
self.percent.set("00.00 %")
self.error = StringVar()
self.STOP = False
self.error.set("---")
self.currentvoltage = StringVar()
self.currentvoltage.set("Current Voltage: 00.00 V")
self.DT = datetime.datetime
# Create and attach labels
label1 = Label(root, text='Voltage')
label2 = Label(root, text='Ramp Duration')
label3 = Label(root, text='Percent Done: ')
label4 = Label(root, textvariable=self.percent)
label5 = Label(root, text="Error Message: ")
label6 = Label(root, textvariable=self.error)
label7 = Label(root, textvariable=self.currentvoltage)
label1.grid(row=0, column=0, sticky=W)
label2.grid(row=1, column=0, sticky=W)
label3.grid(row=2, column=0, sticky=W)
label4.grid(row=2, column=1, sticky=W)
label5.grid(row=3, column=0, sticky=W)
label6.grid(row=3, column=1, sticky=W)
label7.grid(row=3, column=2, sticky=E)
# Create and attach entries
self.voltage = Entry(root)
self.duration = Entry(root)
self.voltage.grid(row=0, column=1)
self.duration.grid(row=1, column=1)
# Create, bind, and attach buttons
HeatButton = Button(root, text='Heat')
HeatButton.bind("<Button-1>", self.Heat)
HeatButton.grid(row=0, column=2)
CoolButton = Button(root, text='Cool')
CoolButton.bind("<Button-1>", self.Heat)
CoolButton.grid(row=1, column=2)
StopButton = Button(root, text='Stop')
StopButton.bind("<Button-1>", self.Stop)
StopButton.grid(row=2, column=2)
def HeatLoop(self, condition, TimeStart, TimeDuration, MaximumVoltage, Fraction=0):
"""Heat up the cell while the condition is true"""
if condition:
self.percent.set("{:2.2f}%".format(Fraction * 100))
print(MaximumVoltage)
self.currentvoltage.set("Current Voltage: {:2.2f} V".format(Fraction*MaximumVoltage))
self.Update()
CurrentTime = self.DT.now()
ElapsedTime = (CurrentTime.second/3600 + CurrentTime.minute/60 + CurrentTime.hour
- TimeStart.second/3600 - TimeStart.minute/60 - TimeStart.hour)
Fraction = ElapsedTime / TimeDuration
print(Fraction)
self.root.after(5000)
self.HeatLoop(bool(not self.STOP and Fraction < 1),
TimeStart, TimeDuration, MaximumVoltage, Fraction)
# Define function to heat up cell
def Heat(self, event):
# Initialize Parameters
self.STOP = False
self.error.set("---")
self.Update()
# Try to get voltage and duration from the GUI
MaxVoltage = self.voltage.get()
TimeDuration = self.duration.get()
try:
MaxVoltage = float(MaxVoltage)
try:
TimeDuration = float(TimeDuration)
except:
self.error.set("Please enter a valid time duration")
self.Update()
self.STOP = True
except:
self.error.set("Please enter a valid voltage value")
self.Update()
self.STOP = True
TimeStart = self.DT.now()
self.HeatLoop(True,
TimeStart, TimeDuration, MaxVoltage)
def Stop(self, event):
self.STOP = True
print("turned off voltage")
def Update(self):
self.root.update_idletasks()
self.root.update()
root1 = Tk()
a = GUIClass(root1)
root1.mainloop()
root.after(5000) is no different than time.sleep(5). It's doing exactly what you're telling it to: to freeze for five seconds.
If you want to run self.HeatLoop every five seconds, the way to do it is like this:
self.root.after(5000, self.HeatLoop,
bool(not self.STOP and Fraction < 1),
TimeStart, TimeDuration, MaximumVoltage,
Fraction)
When you give two or more arguments to after, tkinter will add that function to a queue, and will call that function after the time has expired. This allows the event loop to continue to process events during the five second interval.
A slightly better way to write it would be to check for the condition inside the function rather than passing the condition in, so that the condition is evaluated immediately before doing the work rather than five seconds before doing the work.
For example:
def HeatLoop(self, TimeStart, TimeDuration, MaximumVoltage, Fraction=0):
if self.STOP and Fraction < 0:
return
...
self.root.after(5000, self.HeatLoop,
TimeStart, TimeDuration, MaximumVoltage,
Fraction)
import tkinter as tk
class Timer:
def __init__(self, master):
self.master = master
master.title("Pomodoro Timer")
self.state = False
self.minutes = 25
self.seconds = 0
self.display = tk.Label(master, height=10, width=10, textvariable="")
self.display.config(text="00:00")
self.display.grid(row=0, column=0, columnspan=2)
self.start_button = tk.Button(master, bg="Green", activebackground="Dark Green", text="Start", width=8, height=4, command=self.start())
self.start_button.grid(row=1, column=0)
self.pause_button = tk.Button(master, bg="Red", activebackground="Dark Red", text="Pause", width=8, height=4, command=self.pause())
self.pause_button.grid(row=1, column=1)
self.countdown()
def countdown(self):
"""Displays a clock starting at min:sec to 00:00, ex: 25:00 -> 00:00"""
mins = self.minutes
secs = self.seconds
if self.state == True:
if secs < 10:
if mins < 10:
self.display.config(text="0%d : 0%d" % (mins, secs))
else:
self.display.config(text="%d : 0%d" % (mins, secs))
else:
if mins < 10:
self.display.config(text="0%d : %d" % (mins, secs))
else:
self.display.config(text="%d : %d" % (mins, secs))
if (mins == 0) and (secs == 0):
self.display.config(text="Done!")
else:
if secs == 0:
mins -= 1
secs = 59
else:
secs -= 1
self.master.after(1000, self.countdown())
elif self.state == False:
self.master.after(100, self.countdown())
def start(self):
if self.state == False:
self.state = True
def pause(self):
if self.state == True:
self.state = False
root = tk.Tk()
my_timer = Timer(root)
root.mainloop()
Pretty new to Python in general, am attempting to make what is essentially a simple countdown timer with start and pause abilities.
What I thought would work would be to call the countdown function right when the window was initiated, having it continually check the "state" of the window through a recursive call. When the "state" is False the countdown function would skip over the "timer" portion of the countdown function and call it again to see if the "state" had changed.
When the user clicks the start button the "state" would change to True and the countdown function would now see the "state" had changed and then would begin actually counting down.
Then when the user clicked the pause button the "state" would change to False again and the countdown function would again skip over the "timer" portion of the function and simply call it again.
Issue I keep running into is something like:
RecursionError: maximum recursion depth exceeded in comparison
Not sure how I can get around this error with the implementation I currently have.
Edit:
So changing self.countdown() to self.countdown in both instances (not under init) does fix the error, but now the function simply is "stuck". The desired window appears, but none of the buttons appear to be working.
command=, after() and bind() needs function name without () (sometimes it is called callback)
countdown() is executed by after() like any other function (but later), and it means:
countdown() recreates all local values in every execution so they don't keep values. You have to use self.mins and self.secs to keep values.
inside countdown() you can't use
mins = self.minutes
secs = self.seconds
because it resets time to 25:00 in every execution. You have to set it in start()
Working code
import tkinter as tk
class Timer:
def __init__(self, master):
self.master = master
master.title("Pomodoro Timer")
self.state = False
self.minutes = 25
self.seconds = 0
self.mins = 25
self.secs = 0
self.display = tk.Label(master, height=10, width=10, textvariable="")
self.display.config(text="00:00")
self.display.grid(row=0, column=0, columnspan=2)
self.start_button = tk.Button(master, bg="Green", activebackground="Dark Green", text="Start", width=8, height=4, command=self.start)
self.start_button.grid(row=1, column=0)
self.pause_button = tk.Button(master, bg="Red", activebackground="Dark Red", text="Pause", width=8, height=4, command=self.pause)
self.pause_button.grid(row=1, column=1)
self.countdown()
def countdown(self):
"""Displays a clock starting at min:sec to 00:00, ex: 25:00 -> 00:00"""
if self.state == True:
if self.secs < 10:
if self.mins < 10:
self.display.config(text="0%d : 0%d" % (self.mins, self.secs))
else:
self.display.config(text="%d : 0%d" % (self.mins, self.secs))
else:
if self.mins < 10:
self.display.config(text="0%d : %d" % (self.mins, self.secs))
else:
self.display.config(text="%d : %d" % (self.mins, self.secs))
if (self.mins == 0) and (self.secs == 0):
self.display.config(text="Done!")
else:
if self.secs == 0:
self.mins -= 1
self.secs = 59
else:
self.secs -= 1
self.master.after(1000, self.countdown)
else:
self.master.after(100, self.countdown)
def start(self):
if self.state == False:
self.state = True
self.mins = self.minutes
self.secs = self.seconds
def pause(self):
if self.state == True:
self.state = False
root = tk.Tk()
my_timer = Timer(root)
root.mainloop()
EDIT: as #EL3PHANTEN pointed out you can use string formatting to make it shorter:
text='{:02} : {:02}'.format(self.mins,self.secs)
or
text="%02d : %02d" % (self.mins,self.secs)
I also moved line self.countdown() from __init__ to start() and now I don't need second after() inside countdown().
I also set self.state = False when it displays Done!
import tkinter as tk
class Timer:
def __init__(self, master):
self.master = master
master.title("Pomodoro Timer")
self.state = False
self.minutes = 25
self.seconds = 0
self.mins = 25
self.secs = 0
self.display = tk.Label(master, height=10, width=10, textvariable="")
self.display.config(text="00:00")
self.display.grid(row=0, column=0, columnspan=2)
self.start_button = tk.Button(master, bg="Green", activebackground="Dark Green", text="Start", width=8, height=4, command=self.start)
self.start_button.grid(row=1, column=0)
self.pause_button = tk.Button(master, bg="Red", activebackground="Dark Red", text="Pause", width=8, height=4, command=self.pause)
self.pause_button.grid(row=1, column=1)
def countdown(self):
"""Displays a clock starting at min:sec to 00:00, ex: 25:00 -> 00:00"""
if self.state == True:
if (self.mins == 0) and (self.secs == 0):
self.display.config(text="Done!")
self.state = False
else:
self.display.config(text="%02d:%02d" % (self.mins, self.secs))
if self.secs == 0:
self.mins -= 1
self.secs = 59
else:
self.secs -= 1
self.master.after(1000, self.countdown)
def start(self):
if self.state == False:
self.state = True
self.mins = self.minutes
self.secs = self.seconds
self.countdown()
def pause(self):
if self.state == True:
self.state = False
root = tk.Tk()
my_timer = Timer(root)
root.mainloop()
"{:02} : {:02}".format(10, 0)
i have made a double countdown timer with python and tkinter but it seemed that it cannot be run if the tkinter window is not on the foreground and it cannot simultaneously run. This is my code:
import tkinter as tk
import tkinter.ttk as ttk
import time
class app:
def __init__(self):
self = 0
def mainGUIArea():
def count_down_1():
for i in range(79, -1, -1):
timeCount = "{:02d}:{:02d}".format(*divmod(i, 60))
time_str.set(timeCount)
root.update()
time.sleep(1)
def count_down_2():
for j in range(10, -1, -1):
timeCount = "{:02d}:{:02d}".format(*divmod(j, 60))
time_str1.set(timeCount)
root.update()
time.sleep(1)
#Main Window
root = tk.Tk()
root.title("Super Timer V1.0")
root.minsize(300,300)
root.geometry("500x300")
#Timer1
time_str = tk.StringVar()
label_font = ('Courier New', 40)
tk.Label(root, textvariable = time_str, font = label_font, bg = 'white', fg = 'blue', relief = 'raised', bd=3).pack(fill='x', padx=5, pady=5)
tk.Button(root, text=' Start Timer! ',bg='lightgreen',fg='black', command=count_down_1).pack()
tk.Button(root, text='Stop and Exit',bg='red',fg='white', command=root.destroy).pack()
#Timer2
time_str1 = tk.StringVar()
label_font = ('Courier New', 40)
tk.Label(root, textvariable = time_str1, font = label_font, bg = 'white', fg='blue', relief='raised', bd=3).pack(fill='x', padx=5, pady=5)
tk.Button(root, text=' Start Timer! ',bg='lightblue',fg='black', command=count_down_2).pack()
tk.Button(root, text='Stop and Exit',bg='red',fg='white', command=root.destroy).pack()
def main():
app.mainGUIArea()
main()
Do you have any suggestion? Thank You :)
The calls to time.sleep are at least part of the problem. When you call sleep it does literally that -- it puts the application to sleep. No events can be processed and the GUI freezes. This is the wrong way to do a countdown timer.
The other problem is the calls to update inside the loops alongside the calls to time.sleep. This call will process events, which means that when one of the loops is running and you click a button, you may end up calling the other function, interleaving your two loops.
The proper way to do something periodically is to use after to repeatedly call a function. The general pattern is this:
def update_display(self):
<do whatever code you want to update the display>
root.after(1000, self.update_display)
You can have as many of these running in parallel that you want (up to practical limits, obviously), and your GUI will be completely responsive between updates.
Here's a quick example:
class Countdown(tk.Label):
def __init__(self, parent):
tk.Label.__init__(self, parent, width=5, text="00:00")
self.value = 0
self._job_id = None
def tick(self):
self.value -= 1
text = "{:02d}:{:02d}".format(*divmod(self.value, 60))
self.configure(text=text)
if self.value > 0:
self._job_id = self.after(1000, self.tick)
def start(self, starting_value=60):
if self._job_id is not None: return
self.value = starting_value
self.stop_requested = False
self.after(1000, self.tick)
def stop(self):
self.after_cancel(self._job_id)
self._job_id = None
This is a simple tkinter(gui) stopwatch I made which works perfectly. Check it out
__author__ = 'Surya'
from tkinter import *
import time
class StopWatch(Frame):
def __init__(self, parent = None, ** kw):
Frame.__init__(self, parent, kw)
self._timeelapsed = 0.0
self._start = 0.0
self._run = 0
self.timestr = StringVar()
self.makeWidgets()
def makeWidgets(self):
l = Label(self, textvariable=self.timestr)
self._setTime(self._timeelapsed)
l.pack(fill=X, expand=NO, pady=2, padx=2)
def _update(self):
self._timeelapsed = time.time() - self._start
self._setTime(self._timeelapsed)
self._timer = self.after(50, self._update)
def _setTime(self, elap):
minutes = int(elap/60)
seconds = int(elap - minutes*60.0)
hseconds = int((elap - minutes*60.0 - seconds)*100)
self.timestr.set('%02d:%02d:%02d' % (minutes, seconds, hseconds))
def Start(self):
if not self._run:
self._start = time.time() - self._timeelapsed
self._update()
self._run = 1
def Stop(self):
if self._run:
self.after_cancel(self._timer)
self._timeelapsed = time.time() - self._start
self._setTime(self._timeelapsed)
self._run = 0
def Reset(self):
self._start = time.time()
self._timeelapsed = 0.0
self._setTime(self._timeelapsed)
def main():
root = Tk()
sw = StopWatch(root)
sw.pack(side=TOP)
Button(root, text='Start!', command=sw.Start).pack(side=LEFT)
Button(root, text='Stop!', command=sw.Stop).pack(side=LEFT)
Button(root, text='Reset!!!', command=sw.Reset).pack(side=LEFT)
Button(root, text='Quit!!!', command=root.quit).pack(side=LEFT)
root.mainloop()
if __name__ == '__main__':
main()