Making a countdown timer with Python and Tkinter? - python

I want to set a label in Tkinter using my countdown timer function. Right now all it does is set the lable to "10" once 10 is reached and I don't really understand why. Also, even if I have the timer print to a terminal instead the "Time's up!" bit never prints.
import time
import tkinter as tk
class App():
def __init__(self):
self.root = tk.Tk()
self.label = tk.Label(text="null")
self.label.pack()
self.countdown()
self.root.mainloop()
# Define a timer.
def countdown(self):
p = 10.00
t = time.time()
n = 0
# Loop while the number of seconds is less than the integer defined in "p"
while n - t < p:
n = time.time()
if n == t + p:
self.label.configure(text="Time's up!")
else:
self.label.configure(text=round(n - t))
app=App()

Tkinter already has an infinite loop running (the event loop), and a way to schedule things to run after a period of time has elapsed (using after). You can take advantage of this by writing a function that calls itself once a second to update the display. You can use a class variable to keep track of the remaining time.
import Tkinter as tk
class ExampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.label = tk.Label(self, text="", width=10)
self.label.pack()
self.remaining = 0
self.countdown(10)
def countdown(self, remaining = None):
if remaining is not None:
self.remaining = remaining
if self.remaining <= 0:
self.label.configure(text="time's up!")
else:
self.label.configure(text="%d" % self.remaining)
self.remaining = self.remaining - 1
self.after(1000, self.countdown)
if __name__ == "__main__":
app = ExampleApp()
app.mainloop()

Related

Pause button does not work, how do I use after_cancel properly?

So I am making a program that has a timer and the timer works, now I am working with a pause function. After some research, I found a function called after_cancel. This function supposedly should cancel the after function as the after function in this situation creates an infinite loop. How do I use the after_cancel properly in this situation or are there any other possible solutions?
Thanks in advance.
t = 60000
global timerState
timerState = True
def pause():
timerLabel.after_cancel(countdown)
timerState = False
timerButton.config(text="Play", command=countdown)
def countdown():
global t
if t == 0:
timer = "00:00"
timerLabel.config(text=timer)
return
if timerState == False:
timerLabel.after_cancel(countdown)
timerButton.config(text="Play", command=countdown)
return
mins = t / 60000
secs = t / 1000
secs = secs - int(mins) * 60
mills = t
mills = mills - int(secs) * 1000
if timerState == True:
timer = "{:02d}:{:02d}".format(int(mins),int(secs))
timerLabel.config(text=timer)
t -= 1
timerLabel.after(1, countdown)
timerButton.config(text="Pause", command=pause)
Most of the time .after_cancel scripts can be avoided by just using if statements. For example look at this:
import tkinter as tk
t = 60000
def pause():
global timerState
timerState = False
timerButton.config(text="Play", command=start_countdown)
def start_countdown():
global timerState
timerState = True
timerButton.config(text="Pause", command=pause)
countdown()
def countdown():
global t
if timerState:
timerLabel.config(text=t)
t -= 1
if t > 0:
timerLabel.after(1, countdown)
root = tk.Tk()
timerLabel = tk.Label(root, text="")
timerLabel.pack()
timerButton = tk.Button(root, text="Play", command=start_countdown)
timerButton.pack()
root.mainloop()
I modified your code to show t without making it in the mm:ss format. The main point is that if timerState is False the timerLabel.after(1, countdown) will never be called so there is no point to having a .after_cancel.
Note: You haven't considered the time taken for your other code so t isn't really in milliseconds (at least for my slow computer).
Here is a demonstration of after and after_cancel
Every after needs to be cancelled in order to clear the event queue.
In this program, each time the button is pressed a time delay event is generated.
The event ID is stored in self.after_time
I have set the delay value to increase by 100 ms with each button press, for demo purposes.
it withdraws the master from view.
When the time delay event is complete it calls self.action
self.action cancels the event with after_cancel( self.after_time )
and the master is made visible, ready for the next button press.
import tkinter
class after_demo:
delay = 100
def __init__( self ):
self.master = tkinter.Tk()
self.master.title( 'After Demo' )
self.control = tkinter.Button(
self.master, text = 'Begin Demo',
width = 40, command = self.pause )
self.control.grid(row=0,column=0,sticky='nsew')
def action( self ):
self.master.after_cancel( self.after_time )
self.control[ 'text' ] = 'Delay( {} ) ms'.format( self.delay )
self.master.deiconify()
def pause( self ):
self.after_time = self.master.after( self.delay, self.action )
self.delay += 100
self.master.withdraw()
if __name__ == '__main__':
timer = after_demo( )
tkinter.mainloop()

Tkinter timer to start at 0 on button click

I would like to create a timer that starts at 0 when a user presses a button and stop at whatever time it has displayed when the user presses the button again. So far, all of the questions user after that looks at the current time and updates in seconds from whatever time it is like so:
def timer(self):
now = time.strftime("%H:%M:%S")
self.label.configure(text=now)
self.after(1000, self.timer)
But I would like to start at zero, display the minutes and seconds. Is there anyway to achieve this?
Here's a simple stopwatch GUI. There's some room for improvement. ;)
import tkinter as tk
from time import time
class Stopwatch:
def __init__(self):
root = tk.Tk()
root.title('Stopwatch')
self.display = tk.Label(root, text='00:00', width=20)
self.display.pack()
self.button = tk.Button(root, text='Start', command=self.toggle)
self.button.pack()
self.paused = True
root.mainloop()
def toggle(self):
if self.paused:
self.paused = False
self.button.config(text='Stop')
self.oldtime = time()
self.run_timer()
else:
self.paused = True
self.oldtime = time()
self.button.config(text='Start')
def run_timer(self):
if self.paused:
return
delta = int(time() - self.oldtime)
timestr = '{:02}:{:02}'.format(*divmod(delta, 60))
self.display.config(text=timestr)
self.display.after(1000, self.run_timer)
Stopwatch()
The toggle method toggles the stopwatch on or off. The run_timer method updates the display Label with the time since the timer started, in minutes & seconds. For more accuracy, reduce the .after delay to say, 500, or 100. That will do unnecessary (and invisible) updates to the Label, but the displayed time will be a little more accurate, and the GUI will feel a little more responsive.
import tkinter as tk
import time
class GUI:
def __init__(self, master):
self.root = master
self.parent = tk.Frame(self.root)
self.parent.pack(fill = tk.BOTH)
self.parent.config(bg = "black")
self.now = time.time()
self.buttonVar = tk.IntVar()
self.buttonCycle = False
self.buttonVar.set(0)
self.button = tk.Button(root,
textvariable = self.buttonVar,
command = self.updateButton)
self.button.pack(fill = tk.BOTH)
self.button_cycle()
def updateButton(self):
if self.buttonCycle:
self.buttonCycle = False
self.now = time.time()
elif not self.buttonCycle:
self.buttonCycle = True
def button_cycle(self):
if self.buttonCycle:
now = time.time()
timeDifference = int(now - self.now)
self.buttonVar.set(timeDifference)
self.root.after(1000, self.button_cycle)
root = tk.Tk()
myApp = GUI(root)
root.mainloop()

Update time for recording

I doing a simple python GUI using tkinter to do screen recording.Basically, I am using ffmpeg commands at the backend with tkinter as the front end triggering the ffmpeg commands.There is something that I stuck with.I dont know why my time is unable to trigger off if I program in this way.
The code below is basically the recording method.You will notice that I am actually trying to update my tkinter GUI in the while loop.This method is actually in my class named Gui_Rec() which contains other methods I need for my screen recording program.
def rec(self):
global videoFile
mydate = datetime.datetime.now()
videoFile = mydate.strftime("\%d%b_%Hh%Mm.avi")
self.l['text']=os.path.expanduser('~')+"\Videos"
self.l1['text']=videoFile
self.b.config(state=DISABLED)
self.b1.config(state=ACTIVE)
t = Thread(target=self.rec_thread)#trigger another method using thread which will run ffmpeg commands here
t.start()
while True:
if self.count_flag == False:
break
self.label['text'] = str("%02dm:%02ds" % (self.mins,self.secs))
if self.secs == 0:
time.sleep(0)
else:
time.sleep(1)
if(self.mins==0 and self.secs==1):
self.b1.config(fg="white")
self.b1.config(bg="red")
self.b.config(fg="white")
self.b.config(bg="white")
if self.secs==60:
self.secs=0
self.mins+=1
self.label['text'] = str("%02dm:%02ds" % (self.mins,self.secs))
main.gui.update()
self.secs = self.secs+1
other method in the class Gui_Rec() then this below
def main():
gui = Gui_Rec()
gui.minsize(300,155)
gui.maxsize(390,195)
gui.title("Desktop REC")
gui.attributes("-topmost", 1)
gui.mainloop() #start mainloop of program
if __name__ == '__main__':
main()
Strangely, if I don't put the above section of code in the the def main(), the GUI will be update with the duration of the time running when rec button is pressed.I don't really know how to go about solving this.Tried putting it in another thread yet it doesn't work as well.Thank you everyone for your help.
The while loop is creating a conflict with Tkinter's mainloop. Threading or multiprocessing are solutions, but I'd recommend looking into Tkinter's after() method. Here's a simplified example of how to handle a timer using after:
from Tkinter import *
class App(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.mins = 0
self.secs = 0
# make a stringvar instance to hold the time
self.timer = StringVar()
self.timer.set('%d:%d' % (self.mins, self.secs))
Label(self, textvariable=self.timer).pack()
Button(self, text='Start', command=self._start_timer).pack()
Button(self, text='Stop', command=self._stop_timer).pack()
def _start_timer(self):
self.secs += 1 # increment seconds
if self.secs == 60: # at every minute,
self.secs = 0 # reset seconds
self.mins += 1 # and increment minutes
self.timer.set('%d:%d' % (self.mins, self.secs))
# set up the after method to repeat this method
# every 1000 ms (1 second)
self.repeater = self.after(1000, self._start_timer)
def _stop_timer(self):
self.after_cancel(self.repeater)
root = Tk()
App(root).pack()
mainloop()

How do I make an interuptable program that deals with a heavy computational load?

I am trying to make a program that deals with a heavy computational load in an iterative fashion. I want to be able to interrupt the program and restart.
I have built a simple GUI using Tkinter to allow the iterative loop to be interrupted using an "after_cancel". This works fine in testing with a low computational load, but under a high computational load, the program does not respond to the button click that is meant to do the "after_cancel".
For simplicity's sake, I have replaced the program with fibonacci. The effect is the same (after a while).
How do I design this so that the "Stop" button remains responsive?
Here's the code:
from tkinter import *
import time
def fibonacci(n):
if n == 0:
return 0
if n == 1:
return 1
return fibonacci(n - 1) + fibonacci(n - 2)
class App:
def __init__(self, master):
self.frame = Frame(master)
self.frame.pack(expand=1, fill=BOTH)
self.master = master
self.quitter = Button(self.frame, text="Quit", fg="red", command=self.master.destroy)
self.quitter.pack(side=LEFT)
self.starter = Button(self.frame, text="Start", command=self.start)
self.starter.pack(side=LEFT)
self.stopper = Button(self.frame, text="Stop", command=self.stop)
self.stopper.pack(side=LEFT)
self.counter = 0
def start(self):
print((fibonacci(self.counter)))
self.counter += 1
self.afterid = self.master.after(1, self.start)
def stop(self):
print('Stop')
self.master.after_cancel(self.afterid)
root = Tk()
app = App(root)
root.mainloop()
Your fibonacci function will run on your GUI thread if you call it from a widget callback. To avoid freezing your GUI, run fibonacci on a separate thread and set a flag to stop its execution:
from tkinter import *
import threading
import time
def fibonacci(n):
if n == 0:
return 0
if n == 1:
return 1
return fibonacci(n-1) + fibonacci(n-2)
class App:
def __init__(self, master):
self.frame = Frame(master)
self.frame.pack(expand=1, fill=BOTH)
self.master = master
self.quitter = Button(self.frame, text="Quit", fg="red", command=self.master.destroy)
self.quitter.pack(side=LEFT)
self.starter = Button(self.frame, text="Start", command=self.start)
self.starter.pack(side=LEFT)
self.stopper = Button(self.frame, text="Stop", command=self.stop)
self.stopper.pack(side=LEFT)
self.counter = 0
self.running = False
def start(self):
self.running = True
threading.Thread(target=self.start_fibonacci).start()
def start_fibonacci(self):
while self.running:
print(fibonacci(self.counter))
self.counter += 1
time.sleep(1)
def stop(self):
self.running = False
print('Stop')
if __name__ == "__main__":
root = Tk()
root.title("Fibonacci")
app = App(root)
root.mainloop()
In the mainprog function, you compute the fibonacci once, and then register an alarm callback as self.mainprog, that is called after 1 ms. In other words, there is an interval time between each call to fibonacci function. And your stopprog button could only respond your in the interval time, when the control right is idle.
Under a high computational load, the execution right is always retained by your task (in ONE call of fibonacci function). As a result, no button could respond you.
You would better implement your tast(such as fibonacci computation) in a multi-thread or multi-process manner to avoid this issue.

Launch command in Tkinter based on selected radio button?

I would like to change the function and text of a button based on which radio-button is selected. Right now what's happening is that both commands are run at the same time as soon as the application is launched rather than it being based upon which radio-button is selected.
import tkinter as tk
import time
## Time variables for the Pomodoro
pomo = 60 * 25 ## 60 seconds times number of minutes
btime = 60 * 5 ## 60
class ExampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.label = tk.Label(self, text="25:00", width=10, font="Helvetica 20")
self.label.pack()
self.remaining = 0
self.button = tk.Button(self)
self.button.pack()
pomocommand = self.button.configure(text="Pomodoro", state=tk.NORMAL, command= lambda: self.pomodoro(pomo)) #Switch back to the pomodoro timer
breakcommand = self.button.configure(text="Break", state=tk.NORMAL, command= lambda: self.breaktime(btime)) #Switch to the break timer
countercommand = self.button.configure(text="Counter", state=tk.NORMAL, command= print('cheese'))
self.radvar = tk.IntVar()
self.radvar.set('1')
self.radio = tk.Radiobutton(self, text="Pomodoro", variable = self.radvar, value=1, indicatoron=0, command = pomocommand)
self.radio.pack(anchor=tk.W)
self.radio = tk.Radiobutton(self, text="Counter", variable = self.radvar, value=2, indicatoron=0, command = countercommand)
self.radio.pack(side=tk.LEFT)
def pomodoro(self, remaining = None):
self.button.configure(state=tk.DISABLED)
if remaining is not None:
self.remaining = remaining
if self.remaining <= 0:
self.label.configure(text="Time's up!")
breakcommand
else:
self.label.configure(text= time.strftime('%M:%S', time.gmtime(self.remaining))) #Integer to 'Minutes' and 'Seconds'
self.remaining = self.remaining - 1
self.after(1000, self.pomodoro)
def breaktime(self, remaining = None):
self.button.configure(state=tk.DISABLED)
if remaining is not None:
self.remaining = remaining
if self.remaining <= 0:
self.label.configure(text="Time's up!")
pomocommand
else:
self.label.configure(text= time.strftime('%M:%S', time.gmtime(self.remaining))) #Integer to 'Minutes' and 'Seconds'
self.remaining = self.remaining - 1
self.after(1000, self.breaktime)
if __name__ == "__main__":
app = ExampleApp()
app.mainloop()
What you are doing is calling the function self.button.configure(...) instead of passing the function itself. Here is a small example:
def test(a):
return a+4
callback_1 = test(2) # callback_1 will be (2+4) = 6, because you called the function. Notice the parens ()
callback_2 = test # callback_2 will be the function test, you did not call it
# So, you can do:
callback_2(some_value) # will return (some_value + 4), here you called it
Basically what is happening is that you are using the first example, so the function is called in __init__, and what it is supposed to do is done there. What you want is something similar to the second, because Tkinter wants something to call (a function or any callable). So pomocommand and break should be functions not the result of calling a function. (You can try to do print pomocommand and see that it is not a function.)
The solution is either to create a new function, or use lambdas. Here:
def pomocommand(self):
self.button.configure(text="Pomodoro", state=tk.NORMAL, command= lambda: self.pomodoro(pomo)) #Switch back to the pomodoro timer
# and in your __init__ method:
def __init__(self):
# ...
self.radio = tk.Radiobutton(self, text="Pomodoro", variable = self.radvar, value=1, indicatoron=0, command = self.pomocommand)
# ...
And you do the same for the other button. Using a lambda here is not recommended because it is a long statement (the self.button.configure part) so your code will not be readable. But I see you are using lambdas, so you might get the idea.
As a side note, be careful when using lambda like you do. Here pomo is a global variable, but if it is not, you might get into trouble. See this question. (Right now it is alright, so you can just ignore this :D).

Categories

Resources