0
votes

I am trying to create a Timer using tkinter and python. To start and reset the timer there are two buttons Three Entry fields to get the hours, minutes, and seconds and a Label to display the decreasing countdown time.

But as soon i set a time and click the start button, the time in label is 1 second less than the entered time and the timer ends 1 second after the text in label becomes "00:00:00".Means the time is out of sync by 1 second.

I want the same time to be copied from EntryFields to the label and sync the time.

'''

import time
from tkinter import *
from tkinter.messagebox import *

root = Tk()
root.title("Timer Application")
root.geometry("600x400")

running = 0
hours = StringVar()
mins = StringVar()
secs = StringVar()
string = StringVar()

hrText = Entry(root,font = ("Arial", 20), width=2, textvariable=hours,borderwidth=5)
minText = Entry(root,font = ("Arial", 20), width=2, textvariable=mins,borderwidth=5)
secText = Entry(root,font=("Arial",20), width=2,textvariable=secs,borderwidth=5)
lbl = Label(root, text="00:00:00" , font=('Arial',20),relief=GROOVE,borderwidth=3)

hrText.insert(0,"00")
minText.insert(1,"00")
secText.insert(2,"00")
lbl['text'] = "00:00:00"

hrText.place(x=200,y=100)
minText.place(x=260,y=100)
secText.place(x=320,y=100)


def start():
    running=1
    if set_timer():
        run_timer()

def set_timer():
    if int(hours.get()) >=0 and int(mins.get())>=0 and int(secs.get())>=0:
        string = "{:2s}:{:2s}:{:2s}".format(hours.get(), mins.get(), secs.get())
        lbl['text'] = string
        hrText.config(state=DISABLED)
        minText.config(state=DISABLED)
        secText.config(state=DISABLED)
        time.sleep(1)
        return True
    else:
        showinfo("Wrong Input","Enter a valid time")
        return False
    
    
def run_timer():
    running = 1
    
    h,m,s = [int(x) for x in lbl['text'].split(':')]
    
    tot_secs = h*3600 + m*60 + s
    
    if running:
        if tot_secs:
            
            if s>0:
                s-=1
            elif s==0:
                if m>0:
                    m-=1
                    s=59
                elif m==0:
                    if h:
                        h-=1
                        m=59
                        s=59
            if tot_secs:
                lbl['text'] = str(h)+":"+str(m)+":"+str(s)
                lbl.after(1000,run_timer)
            else:
                return
            # if  tot_secs>0:     
        elif tot_secs==0:
            running=0
            showinfo(title="Complete",message="Time's Up.")
            return None

        

def reset():
    running=0
    hrText.config(state=NORMAL)
    minText.config(state=NORMAL)
    secText.config(state=NORMAL)
    # secText.config(setfocus=1)
    hours.set("00")
    mins.set("00")
    secs.set("00")
    lbl['text']="00:00:00"
    
lbl.place(x = 180, y= 250, width= 200)  
bt = Button(root, text="Start",font=("Consolas", 16),command = start).place(x=300, y= 182)
bt = Button(root, text="Reset", font=("Consolas", 16),command=reset).place(x=180, y = 182)
root.mainloop()

'''

1
The first issue is that you have called time.sleep(1) after you set the label text, then run_timer() is executed which decrease the timer by 1 and update the label text. That's why you cannot see the initial time. Add lbl.update() before time.sleep(1) to fix it. The second issue is that when tot_secs is 1 when run_timer() is executed, the timer will be decreased by 1 so that h=m=s=0 and the label is updated with "0:0:0" and call lbl.after(1000, run_timer). So one second later, run_timer() is called and tot_secs is zero and then show the "Time's Up" message. - acw1668

1 Answers

0
votes

The problem is that you start substracting from the seconds without waiting the 1000ms (lbl.after(1000,run_timer)). You can just solve this issue, by passing a boolean argument first_time to the function run_timer, which is set to False by default and you pass it True the first time you call the function. I have also fixed the issue of calling the after function the last time, when you should not. Your fixed code would be:

import time
from tkinter import *
from tkinter.messagebox import *

root = Tk()
root.title("Timer Application")
root.geometry("600x400")

running = 0
hours = StringVar()
mins = StringVar()
secs = StringVar()
string = StringVar()

hrText = Entry(root,font = ("Arial", 20), width=2, textvariable=hours,borderwidth=5)
minText = Entry(root,font = ("Arial", 20), width=2, textvariable=mins,borderwidth=5)
secText = Entry(root,font=("Arial",20), width=2,textvariable=secs,borderwidth=5)
lbl = Label(root, text="00:00:00" , font=('Arial',20),relief=GROOVE,borderwidth=3)

hrText.insert(0,"00")
minText.insert(1,"00")
secText.insert(2,"00")
lbl['text'] = "00:00:00"

hrText.place(x=200,y=100)
minText.place(x=260,y=100)
secText.place(x=320,y=100)


def start():
    running=1
    if set_timer():
        run_timer(True)

def set_timer():
    if int(hours.get()) >=0 and int(mins.get())>=0 and int(secs.get())>=0:
        string = "{:2s}:{:2s}:{:2s}".format(hours.get(), mins.get(), secs.get())
        lbl['text'] = string
        hrText.config(state=DISABLED)
        minText.config(state=DISABLED)
        secText.config(state=DISABLED)
        time.sleep(1)
        return True
    else:
        showinfo("Wrong Input","Enter a valid time")
        return False
    
    
def run_timer(first_time=False):
    running = 1
    
    h,m,s = [int(x) for x in lbl['text'].split(':')]
    
    tot_secs = h*3600 + m*60 + s
    
    if running:
        if tot_secs:
            
            if s>0:
                if not first_time:
                    s-=1
            elif s==0:
                if m>0:
                    m-=1
                    s=59
                elif m==0:
                    if h:
                        h-=1
                        m=59
                        s=59
            if tot_secs:
                lbl['text'] = str(h)+":"+str(m)+":"+str(s)
                if tot_secs>1:
                    lbl.after(1000,run_timer)
                else:
                    running=0
                    showinfo(title="Complete",message="Time's Up.")
                    return None
            else:
                return

def reset():
    running=0
    hrText.config(state=NORMAL)
    minText.config(state=NORMAL)
    secText.config(state=NORMAL)
    # secText.config(setfocus=1)
    hours.set("00")
    mins.set("00")
    secs.set("00")
    lbl['text']="00:00:00"
    
lbl.place(x = 180, y= 250, width= 200)  
bt = Button(root, text="Start",font=("Consolas", 16),command = start).place(x=300, y= 182)
bt = Button(root, text="Reset", font=("Consolas", 16),command=reset).place(x=180, y = 182)
root.mainloop()