2

I have a countdown program (code below) that should start counting down from whatever length of time you have put in (you all know how a timer works, right?). But the tkinter window simply freezes when you press start but does not return any error until a windows error message saying that the window is not responding pops up. I have included some print statements as I was trying to debug it and it shows in shell that the program is working as it repeatedly prints c .Does anyone know how to sort out this problem?

import tkinter as tk
import time

def change(direction, stringvar, up_button, down_button):
    if direction == 'up':
        stringvar.set(stringvar.get()+1)
        down_button.config(state = 'normal')
        if int(stringvar.get())== 59:
            up_button.config(state = 'disabled')
    if direction == 'down':
        stringvar.set(stringvar.get()-1)
        up_button.config(state = 'normal')
        if stringvar.get()== 0:
            down_button.config(state = 'disabled') 

def startTimer():
    hourEntry.destroy()
    minuteEntry.destroy()
    secondEntry.destroy()
    print('a')
    hourLab = tk.Label(root, textvariable = hourText)
    minuteLab = tk.Label(root, textvariable = minuteText)
    secondLab = tk.Label(root, textvariable = secondText)
    print('b')
    hourLab.grid(row = 1, column = 0)
    minuteLab.grid(row = 1, column = 1)
    secondLab.grid(row = 1, column = 2)
    while hourText.get() != 0 or minuteText != 0 or secondText !=0:
        print('c')
        time.sleep(1)
        secondText.set(secondText.get()-1)
        if int(secondText.get()) == 0:
            secondText.set(59)
            if int(minuteText.get()) == 0:
                if int(hourText.get()) == 0:
                    continue
                else:
                    hourText.set(str(int(hourText.get())-1))
            else:
                minuteText.set(str(int(minuteText.get())-1))
root = tk.Tk()
root.title('Timer')
hourText = tk.IntVar()
minuteText = tk.IntVar()
secondText = tk.IntVar()
hourText.set(1)
minuteText.set(1)
secondText.set(1)
## create buttons and entry boxes using loop
up1 = tk.Button(root, text = '^^^', command = lambda: change('up', hourText, up1, down1))
up2 = tk.Button(root, text = '^^^', command = lambda: change('up', minuteText, up2, down2))
up3 = tk.Button(root, text = '^^^', command = lambda: change('up', secondText, up3, down3))
hourEntry = tk.Entry(root, textvariable = hourText, width = 5)
minuteEntry = tk.Entry(root, textvariable = minuteText, width = 5)
secondEntry = tk.Entry(root, textvariable = secondText, width = 5)
down1 = tk.Button(root, text = '...', command = lambda: change('down', hourText, up1, down1))
down2 = tk.Button(root, text = '...', command = lambda: change('down', minuteText, up2, down2))
down3 = tk.Button(root, text = '...', command = lambda: change('down', secondText, up3, down3))
start = tk.Button(root, text = 'Start', command = startTimer)
up1.grid(row = 0, column = 0, pady = 5, padx = 5)
up2.grid(row = 0, column = 1)
up3.grid(row = 0, column = 2, padx = 5)
hourEntry.grid(row = 1, column = 0, padx = 2, pady = 5)
minuteEntry.grid(row = 1, column = 1, padx = 2, pady = 5)
secondEntry.grid(row = 1, column = 2, padx = 2, pady = 5)
down1.grid(row = 2, column = 0)
down2.grid(row = 2, column = 1)
down3.grid(row = 2, column = 2)
start.grid(row = 3, columnspan = 3, pady = 5)
root.mainloop()
Sam Varghese
  • 428
  • 1
  • 5
  • 14
  • Of course, you used a ```while``` loop. This will cause the ```mainloop``` to not respond –  Jun 28 '21 at 15:57
  • I suggest you to refer to this site https://stackoverflow.com/questions/2400262/how-can-i-schedule-updates-f-e-to-update-a-clock-in-tkinter –  Jun 28 '21 at 15:59
  • In general you should only have functions and methods which do something quickly and return when you are using a GUI framework. The frameworks generally work with events. You could use something like `.after()` to schedule calls to a function which does the `hourText.set(..` stuff. – quamrana Jun 28 '21 at 15:59

2 Answers2

1

To implement your program :

import tkinter as tk
import time

def change(direction, stringvar, up_button, down_button):
    if direction == 'up':
        stringvar.set(stringvar.get()+1)
        down_button.config(state = 'normal')
        if int(stringvar.get())== 59:
            up_button.config(state = 'disabled')
    if direction == 'down':
        stringvar.set(stringvar.get()-1)
        up_button.config(state = 'normal')
        if stringvar.get()== 0:
            down_button.config(state = 'disabled') 

def timer():
    if hourText.get() != 0 or minuteText != 0 or secondText !=0:
        print('c')
        time.sleep(1)
        secondText.set(secondText.get()-1)
        if int(secondText.get()) == 0:
            secondText.set(59)
            if int(minuteText.get()) == 0:
                if int(hourText.get()) == 0:
                    pass
                else:
                    hourText.set(str(int(hourText.get())-1))
            else:
                minuteText.set(str(int(minuteText.get())-1))
        root.after(1, timer)

def startTimer():
    hourEntry.destroy()
    minuteEntry.destroy()
    secondEntry.destroy()
    print('a')
    hourLab = tk.Label(root, textvariable = hourText)
    minuteLab = tk.Label(root, textvariable = minuteText)
    secondLab = tk.Label(root, textvariable = secondText)
    print('b')
    hourLab.grid(row = 1, column = 0)
    minuteLab.grid(row = 1, column = 1)
    secondLab.grid(row = 1, column = 2)
    root.after(1, timer)
        
root = tk.Tk()
root.title('Timer')
hourText = tk.IntVar()
minuteText = tk.IntVar()
secondText = tk.IntVar()
hourText.set(1)
minuteText.set(1)
secondText.set(1)
## create buttons and entry boxes using loop
up1 = tk.Button(root, text = '^^^', command = lambda: change('up', hourText, up1, down1))
up2 = tk.Button(root, text = '^^^', command = lambda: change('up', minuteText, up2, down2))
up3 = tk.Button(root, text = '^^^', command = lambda: change('up', secondText, up3, down3))
hourEntry = tk.Entry(root, textvariable = hourText, width = 5)
minuteEntry = tk.Entry(root, textvariable = minuteText, width = 5)
secondEntry = tk.Entry(root, textvariable = secondText, width = 5)
down1 = tk.Button(root, text = '...', command = lambda: change('down', hourText, up1, down1))
down2 = tk.Button(root, text = '...', command = lambda: change('down', minuteText, up2, down2))
down3 = tk.Button(root, text = '...', command = lambda: change('down', secondText, up3, down3))
start = tk.Button(root, text = 'Start', command = startTimer)
up1.grid(row = 0, column = 0, pady = 5, padx = 5)
up2.grid(row = 0, column = 1)
up3.grid(row = 0, column = 2, padx = 5)
hourEntry.grid(row = 1, column = 0, padx = 2, pady = 5)
minuteEntry.grid(row = 1, column = 1, padx = 2, pady = 5)
secondEntry.grid(row = 1, column = 2, padx = 2, pady = 5)
down1.grid(row = 2, column = 0)
down2.grid(row = 2, column = 1)
down3.grid(row = 2, column = 2)
start.grid(row = 3, columnspan = 3, pady = 5)
root.mainloop()

Tkinter uses a method (Tk.after) that allow the user to overcome the tk mainloop. (The window does not wait for the function to finish).

It allows us to update manually all the widgets (canvas, button, label [ect...])

This is a method I always use in my tkinter applications, because I don't trust tk.mainloop () to do what I want.

wartonbega
  • 129
  • 6
  • Thank you for this code snippet, which might provide some limited, immediate help. A [proper explanation](https://meta.stackexchange.com/q/114762/349538) would greatly improve its long-term value by showing why this is a good solution to the problem and would make it more useful to future readers with other, similar questions. Please [edit] your answer to add some explanation, including the assumptions you’ve made. – Delrius Euphoria Jun 28 '21 at 17:31
-1

It is beacause you used a while loop. Your window is waiting for it to end.

You should use intstead a window.after(time, target) :

import tkinter
window = tk.Tk()
def task():
    # do things here
    window.after(1, task)

window.after(100, task)
window.mainloop()
wartonbega
  • 129
  • 6