0

I'm quite new to python programming and I was attempting to create a countdown timer. I wrote down the following code for the timer but it's not working. Can you help in figuring out where the error is? (I did a bit of research on this site and found a few questions relating to timers. In fact most of this code is written based on another answer but I did some changes to it because that code was for a clock and I'm trying to make a timer.)

from tkinter import *
from tkinter import messagebox

class alarmclock():
    
    def __init__(self):
        self.alarm = Tk()
        self.l0 = Label(self.alarm, text = '----------- Alarm clock 1 -----------')
        self.l1 = Label(self.alarm, text = 'Please type in the time:')
        self.e0 = Entry(self.alarm)
        self.l2 = Label(self.alarm)
        self.b0 = Button(self.alarm, text = 'Start countdown', command = lambda: self.timer)
        self.l0.grid(row = 0, column = 0, columnspan = 2)
        self.l1.grid(row = 1, column = 0, padx = 10, pady =5)
        self.e0.grid(row = 1, column = 1, padx = 10)
        self.l2.grid(row = 2, column = 1, padx = 10)
        self.b0.grid(row = 3, column = 0, columnspan = 2, pady = 5)
        self.alarm.mainloop()
    
    def timer(self):
        self.b = float(self.e0.get())
        self.l2.configure(text = str(self.b))
        self.b-=1
        if self.b != 0:
            self.alarm.after(1000, self.timer)
        if self.b == 0:
            self.l2.configure(text = str(self.b))
            self.messagebox.showwarning('Alarm Clock', 'Time\'s up!')
8
  • What is the error? Did you instantiate the class? Commented Jun 17, 2021 at 15:06
  • @CoolCloud on clicking the button nothing happens Commented Jun 17, 2021 at 15:07
  • command = lambda: self.timer() then Commented Jun 17, 2021 at 15:08
  • Or perhaps just command = self.timer. Note the lack of parentheses. Commented Jun 17, 2021 at 15:08
  • self.b = float(self.e0.get())....check your logic. Commented Jun 17, 2021 at 15:10

3 Answers 3

5

What about something like this?

from tkinter import *
from tkinter import messagebox

class alarmclock():
    
    def __init__(self):
        self.alarm = Tk()
        self.l0 = Label(self.alarm, text = '----------- Alarm clock 1 -----------')
        self.l1 = Label(self.alarm, text = 'Please type in the time:')
        self.e0 = Entry(self.alarm)
        self.countdown = StringVar(self.alarm, "")
        self.l2 = Label(self.alarm, textvariable=self.countdown)
        self.b0 = Button(self.alarm, text = 'Start countdown', command = lambda : self.timer(reset=True))
        self.l0.grid(row = 0, column = 0, columnspan = 2)
        self.l1.grid(row = 1, column = 0, padx = 10, pady =5)
        self.e0.grid(row = 1, column = 1, padx = 10)
        self.l2.grid(row = 2, column = 1, padx = 10)
        self.b0.grid(row = 3, column = 0, columnspan = 2, pady = 5)
        self.alarm.mainloop()
    
    def timer(self, reset=False):
        if reset:
            self.b = float(self.e0.get())
        self.countdown.set(str(self.b))
        self.b-=1
        if self.b >= 0:
            self.alarm.after(1000, self.timer)
        else:
            messagebox.showwarning('Alarm Clock', 'Time\'s up!')

if __name__ == '__main__':
    my_alarm = alarmclock()

Notice that:

  • you were not actually calling the timer function due to an error in your lambda function definition
  • you were resetting the countdown value at each update
  • checking for b==0 after having decreased b caused the timer to not display the "0"
  • using self.messagebox.showwarning(_) throws an error AttributeError: 'alarmclock' object has no attribute 'messagebox'; messagebox.showwarning(_) works instead
  • I have used a StringVar element for convenience (not necessary)

By the way, I suggest you to NOT import modules using *. Try using import tkinter as tk instead.

Sign up to request clarification or add additional context in comments.

4 Comments

Why do you not use self. before messagebox (it does work but what is the reason)?
Simply because of this error: AttributeError: 'alarmclock' object has no attribute 'messagebox'. Sorry I forgot to mention it in the answer
There is no need to use a extra StringVar here
@CoolCloud right, but I think it makes it easier to understand. Just a personal preference
2

Firstly to answer your main question: tkinter requires a function that it can call if you press the button, so you have to supply the function so tkinter can call it, here you are are supplying a function with lambda that returns the function, you can want to call. So when you press the button the lambda function is called and the function(you wanted to call) is returned, but not called. TL;DR, use:

command = lambda: self.timer()

Secondly, the logic is flawed because you are setting the time to self.b each time the function is ran, instead pass in an argument:

self.b0 = Button(self.alarm,....,command=lambda: self.timer(self.e0.get()))

def timer(self, time):
    time = float(time)
    self.l2.configure(text=time)

    if time > 0:
        time -= 1
        self.alarm.after(1000, self.timer, time)
    else:
        self.l2.configure(text=time)
        messagebox.showwarning('Alarm Clock', 'Time\'s up!')

Comments

1

try my timer:

import time

from tkinter import messagebox
class PDTkClock(Exception):
__module__ = Exception.__module__

class Timer:
"""
valid parameter ::--
  year, month, week, day, hour, minute, second

"""
_value = ("year", 'month', "week", "day", "hour", "minute", "second")

def __init__(self, tk_widget, **kwargs):
    self._sec = 0
    self.time_over = False
    if self._check(**kwargs) and not self.time_over:
        for i in kwargs:
            if i == "year":
                val = kwargs[i]
                self._sec += val * 31556952
            elif i == "month":
                val = kwargs[i]
                self._sec += val * 2628000
            elif i == "week":
                val = kwargs[i]
                self._sec += val * 604800
            elif i == "day":
                val = kwargs[i]
                self._sec += val * 86400
            elif i == "hour":
                val = kwargs[i]
                self._sec += val * 3600
            elif i == "minute":
                val = kwargs[i]
                self._sec += val * 60
            elif i == "second":
                val = kwargs[i]
                self._sec += val * 1
        self._time_it(tk_widget, self._sec)
    else:
        raise PDTkClock(f"kwargs must be {self._value}")

def _time_it(self, tk_widget, sec):
    _ = sec

    year = sec // 31556952
    sec -= year * 31556952

    month = sec // 2628000
    sec -= month * 2628000

    week = sec // 604800
    sec -= week * 604800

    day = sec // 86400
    sec -= day * 86400

    hour = sec // 3600
    sec -= hour * 3600

    minute = sec // 60
    sec -= minute * 60

    second = sec // 1
    get = f"{year}:{month}:{week}:{day}:{hour}:{minute}:{second}"
    if _ >=0 and not self.time_over:
        tk_widget['text'] = get
        tk_widget.update()
        tk_widget.after(1000, self._time_it, tk_widget, _ - 1)
    else:
        messagebox.showwarning('Alarm Clock', 'Time\'s up!')

def _check(self, **kwargs):
    ch = []
    for i in kwargs:
        if i in self._value:
            ch.append(True)
        else:
            ch.append(False)
            break
    return all(ch)
from tkinter import Tk, Label

r = Tk()

l1 = Label(r)
l1.pack()
Timer(l2, second=10, minute=0)
r.mainloop()

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.