Search code examples
pythondebuggingtkinterstopwatch

Tkinter stopwatch stops with 1 second delay


When you press "reset" button, the stopwatch is supposed to stop and reset, but instead it sets digits to zero, then adds one more second and ends up with 00:00:01.

The same with "stop" button. F.e. if we press stop at 00:05, it stops with 00:06. Plus one second every time. But sometimes you can click at the right moment, at the very beginning of the current second so it won't happen.

I tried after_cancel, didn't help.

It's impossible to run debugging in VScode because break points block buttons while the time is going, so I can't trigger the function.

I followed several tutorials and every time I implement pieces of their code, I get this issue.

Why does it happen? Where this 1 second comes from and how to fix?

from tkinter import *

root = Tk()

switch = False

def start():
    global switch
    switch = True

def stop():
    global switch
    switch = False


# Stopwatch variables
seconds = 0
minutes = 0
hours = 0

# Stopwatch functions

def stopwatch_update():
            global seconds, minutes, hours
            seconds += 1
            if seconds == 60:
                minutes += 1
                seconds = 0
            if minutes == 60:
                hours += 1
                minutes = 0
            
            hours_string = f'{hours}' if hours > 9 else f'0{hours}'
            minutes_string = f'{minutes}' if minutes > 9 else f'0{minutes}'
            seconds_string = f'{seconds}' if seconds > 9 else f'0{seconds}'

            stopwatch_label.config(text = hours_string + ':' + minutes_string + ':' + seconds_string)
            if switch:
                stopwatch_label.after(1000, stopwatch_update)

def stopwatch_func(command):
    if command == 'start':
        start()
        stopwatch_start.config(state='disabled')
        stopwatch_stop.config(state='normal')
        stopwatch_reset.config(state='normal')
        stopwatch_update()


    if command == 'stop':
        stopwatch_start.config(state='normal')
        stopwatch_stop.config(state='disabled')
        stop()

    if command == 'reset':
        stopwatch_start.config(state='normal')
        stopwatch_stop.config(state='disabled')
        stop()
        global hours, minutes, seconds
        hours, minutes, seconds = 0, 0, 0
        stopwatch_label.config(text='00:00:00')

                

# Stopwatch components
stopwatch_label = Label(root, font='calibri 20', text='Stopwatch')
stopwatch_label.pack()
stopwatch_start = Button(root, text='Start', command=lambda: stopwatch_func('start'))
stopwatch_start.pack()
stopwatch_stop = Button(root, text='Stop', state='disabled',command=lambda:stopwatch_func('stop'))
stopwatch_stop.pack()
stopwatch_reset = Button(root, text='Reset', state='disabled', command=lambda:stopwatch_func('reset'))
stopwatch_reset.pack()


mainloop()

Solution

  • Defined a variable for stopwatch_label.after(1000, stopwatch_update) and now everything works fine.

    The end of stopwatch_update():

    if switch:
              global stopwatch_after
              stopwatch_after = stopwatch_label.after(1000, stopwatch_update)
    

    stopwatch_func(command) is also updated for "stop" and "reset" commands:

    if command == 'stop':
            stopwatch_start.config(state='normal')
            stopwatch_stop.config(state='disabled')
            stop()
            stopwatch_label.after_cancel(stopwatch_after)
    
    if command == 'reset':
            stopwatch_start.config(state='normal')
            stopwatch_stop.config(state='disabled')
            stop()
            global hours, minutes, seconds
            hours, minutes, seconds = 0, 0, 0
            stopwatch_label.config(text='00:00:00')
            stopwatch_label.after_cancel(stopwatch_after)