Python, count down timer that doesn't sleep - python

I am new to python and i am trying to make a countdown timer on a button click. But i would like this countdown timer to start its countdown and place the current countdown value in the text area. Also i need the rest of the application to not sleep while this countdown is running. So far it will output the countdown in the console but will freeze the rest of the application. Can someone point me in the right direction?
from Tkinter import *
import time
import threading
import thread
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.getvalue = Button(frame, text="Get the Text Area", command=self.thevalue)
self.getvalue.pack(side=LEFT)
self.text_area = Entry()
self.text_area.pack(side=RIGHT)
def thevalue(self):
print "In the value"
try:
t = threading.Thread(target=self.print_time("I am in print_time"))
t.daemon = True
t.start()
except:
print "Error: unable to start thread"
def print_time(self,bleh):
print bleh
print "The text area value is %s" % self.text_area.get()
boom=5
while boom >0:
time.sleep(1)
self.text_area.delete(0, END)
self.text_area.insert(0, boom)
print(boom)
boom -=1
root = Tk()
app = App(root)
root.mainloop()

threading.Thread(target=self.print_time("I am in print_time"))
This will not do what you want it to do. What happens here is that the function self.print_time is called and its return value is then passed to the constructor of threading.Thread.
You need to create the thread like this:
t = threading.Thread(target=self.print_time, args=("I am in print_time",))

Related

Sleep while loop without break it, for write new entry in tkinter

In this UI there is 2 buttons and 1 entry box. Buttons named "Start Loop" and "Print".
When i write a text into the entry box, i should able to see it's print out when pressed the related button. What i am trying to do is, put that button press in a while loop. But the interesting part is trying to print a new entry in every 10 seconds.
When you entered a entry and press the "Start Loop" button, it becomes run. Meanwhile user interface window will froze. Can't write a new entry or press "Print" button. Even i use time.sleep function it still frozen. It prints old entry, but i want to write a new entry at every iteration. Check out the code:
import time
from tkinter import *
class tkin(Frame):
def __init__(self,parent):
Frame.__init__(self, parent)
self.parent = parent
self.UI()
def UI(self):
self.down = StringVar()
self.down_entry = Entry(self, textvariable=self.down)
self.down_entry.grid(row=2, column=0)
self.start_loop_buuton = Button(text="Start Loop", command=self.loop_func)
self.start_loop_buuton.place(x=10,y=40)
self.print_func_button = Button(text="Print ", command=self.pprint)
self.print_func_button.place(x=120,y=40)
self.pack()
def loop_func(self):
start = time.time()
while True:
print("standart print out")
end = time.time()
if (end- start) >10:
time.sleep(10)
self.print_func_button.invoke() ## press print_func_button
start = time.time()
def pprint(self):
print("WHICH PRINT LINE I WANT TO PRINT IN LIKE EVERY 10 SECONDS")
print(self.down.get())
def main():
root = Tk()
tkin(root)
root.geometry("195x100+300+300")
root.mainloop()
main()
Any advice would be nice. Thanks in advance.
this is how I would redefine Your method:
def loop_func(self):
self.print_func_button.invoke() # press print_func_button
self.after(3000, self.loop_func)
time is in miliseconds so this will be 3 seconds
In this example, i just separated the looping "loop_func()" using thread in Button function's argument of command. Like that:
import threading
...
...
...
self.start_loop_buuton = Button(text="Start Loop", command=threading.Thread(target=self.loop_func).start())
So, these two loops have been separated.

mainloop overides while loop in tkinter

Everything works but the time does not update and I think it is because the mainloop overrides the while loop. Please help, I have searched for a long time and found nothing.
While loop is below then main code:
def loop1():
Time = time.strftime("%H:%M:%S")
while Time != Alarm:
Time = time.strftime("%H:%M:%S")
alarm = Tk()
label3 = Label(alarm, text=Time)
label3.grid(column=0, row=0)
alarm.mainloop()
#Get new time
Time = time.strftime("%H:%M:%S")
#Change to next second
label3.config(text=Time)
Main code:
#Import libraries
from tkinter import *
import time
Time = time.strftime("%H:%M:%S")
def loop1():
Time = time.strftime("%H:%M:%S")
while Time != Alarm:
Time = time.strftime("%H:%M:%S")
alarm = Tk()
label3 = Label(alarm, text=Time)
label3.grid(column=0, row=0)
alarm.mainloop()
#Get new time
Time = time.strftime("%H:%M:%S")
#Change to next second
label3.config(text=Time)
initalarm = Tk()
label1 = Label(initalarm,text="What time do you want to wake up?")
label2 = Label(initalarm,text="Use this form.\nExample: 06:30:00")
Alarm = Entry()
start = Button(initalarm, text="Set Alarm", command=loop1)
label1.pack()
label2.pack()
Alarm.pack()
start.pack()
mainloop doesn't override anything. It simply will not return until the root window is destroyed. Or more correctly, it won't return until root.quit() is called, which happens automatically when the root window is destroyed.
Don't fight against the framework, use it. GUI programming with Tk is event driven, i.e. you don't control the control flow of your program directly in long running loops. Instead you set up handlers for different events which get called from Tk's main loop when the events occur. Examples of events are button presses or when a certain given time has passed. This can be used to regularly get some handler called by the main loop for updating the display and eventually activating the alarm.
import tkinter as tk
from tkinter.font import nametofont
from tkinter.simpledialog import askstring
from datetime import datetime as DateTime
def update_display(time_label, alarm_time):
now = DateTime.now().strftime('%H:%M:%S')
if now >= alarm_time:
time_label['foreground'] = 'yellow'
time_label['text'] = now
time_label.after(500, update_display, time_label, alarm_time)
def main():
root = tk.Tk()
root.title('Alarm Clock')
font = nametofont('TkTextFont').copy()
font['size'] *= 5
time_label = tk.Label(root, text='--:--:--', font=font)
time_label.pack()
alarm_time = askstring(
'When?', 'What time do you want to wake up?\nExample: 06:30:00'
)
if alarm_time is not None:
update_display(time_label, alarm_time)
root.mainloop()
if __name__ == '__main__':
main()
The code does approximately what was tried with the code in the question if I got it right, but an actual useful alarm clock program needs object oriented programming in Python, if it should not become a hard to understand and follow mess.
Also I would not operate on the times as strings but as objects from the datetime module, or at least as number of seconds since ”epoch” with the time module. The string form is better left to input and output for the user. And the user input should be validated. In the current form it is too easy to enter invalid alarm times by accident.
As per my understanding you are looking for the label to be updated till it reaches alarm time. If that's true than there are two thing wrong with your code
alarm = Tk() should be moved out of the while loop
You should use alarm.update() instead of alarm.mainloop()
Updated loop1 definition:
def loop1():
Time = time.strftime("%H:%M:%S")
alarm = Tk()
while Time != Alarm:
Time = time.strftime("%H:%M:%S")
label3 = Label(alarm, text=Time)
label3.grid(column=0, row=0)
alarm.update()
#Get new time
Time = time.strftime("%H:%M:%S")
#Change to next second
label3.config(text=Time)
Main code:
#Import libraries
from tkinter import *
import time
Time = time.strftime("%H:%M:%S")
def loop1():
Time = time.strftime("%H:%M:%S")
alarm = Tk()
while Time != Alarm:
Time = time.strftime("%H:%M:%S")
label3 = Label(alarm, text=Time)
label3.grid(column=0, row=0)
alarm.update()
#Get new time
Time = time.strftime("%H:%M:%S")
#Change to next second
label3.config(text=Time)
initalarm = Tk()
label1 = Label(initalarm,text="What time do you want to wake up?")
label2 = Label(initalarm,text="Use this form.\nExample: 06:30:00")
Alarm = Entry()
start = Button(initalarm, text="Set Alarm", command=loop1)
label1.pack()
label2.pack()
Alarm.pack()
start.pack()
The way that root.mainloop() or in your case alarm.mainloop() works is that it will make it's own loop that will update the root (or alarm) window. Whenever a command is run (like a root.bind, or a button command) it will pause the mainloopBecause mainloop is looping, doing it's own thing, it will never reach lines past the root.mainloop() or alarm.mainloop() (unless some weird errors occur).A really easy way to fix that is to make your own "mainloop" by having a loop that does your things and then uses root.update() or alarm.update() (which is what is being run over and over by the mainloop command but you can "customize" the loop)
Using this will still allow the binds and button commands to work. Instead of calling alarm.mainloop() before your loop, call alarm.update() inside of your loop

Python Set Button Text While busy

I'm new to python and I am trying to create a program but I can't even get the basics right. I have a button app that looks like this:
#simple GUI
from tkinter import *
import time
#create the window
root = Tk()
#modify root window
root.title("Button Example")
root.geometry("200x50")
button1state = 0
def start():
count = 0
button1["text"] ="Busy!"
while (count < 5):
root.after(1000)
count = count + 1
def button1clicked():
global button1state
if button1state == 0:
start()
button1["text"] ="On!"
button1state = 1
else:
button1["text"] ="Off!"
button1state = 0
app = Frame(root)
app.pack()
button1 = Button(app, text ="Off!", command = button1clicked)
button1.pack()
#kick off the event loop
root.mainloop()
Now everything works except it doesn't change the button text to busy while
**start()** is called. How can I fix this? Once I've got it working I want to use images to show the user that its OFF ON and BUSY. Please help me
You need to force the GUI to update before starting the task:
def start():
count = 0
button1.configure(text="Busy!")
root.update() # <-- update window
while (count < 5):
root.after(1000)
count = count + 1
But if you don't want your GUI to be frozen while the task is executed, you will need to use a thread as Dedi suggested.
You have to make a thread in order to make you function as a "background event" while your interface is working. Consider using that :
from threading import Thread
and then :
my_thread=Thread(target=start())
my_thread.start()
Where the first "start()" is the name of your function and the second one a call for the thread to begin.

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 to connect a progress bar to a function?

I'm trying to connect a progress bar to a function for my project.
This is what I have so far but im pretty sure it does nothing:
def main():
pgBar.start()
function1()
function2()
function3()
function4()
pgBar.stop()
Here is the code where I make my progress bar if that helps at all:
pgBar = ttk.Progressbar(window, orient = HORIZONTAL, length=300, mode = "determinate")
pgBar.place(x=45, y=130)
I have been doing some research and understand that the tkinter window freezes when running a function or something like that. Is there a way I could "unfreeze" the window at the end of each function that is called inside the main one?
Since tkinter is single threaded, you need another thread to execute your main function without freezing the GUI. One common approach is that the working thread puts the messages into a synchronized object (like a Queue), and the GUI part consumes this messages, updating the progress bar.
The following code is based on a full detailed example on ActiveState:
import tkinter as tk
from tkinter import ttk
import threading
import queue
import time
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.queue = queue.Queue()
self.listbox = tk.Listbox(self, width=20, height=5)
self.progressbar = ttk.Progressbar(self, orient='horizontal',
length=300, mode='determinate')
self.button = tk.Button(self, text="Start", command=self.spawnthread)
self.listbox.pack(padx=10, pady=10)
self.progressbar.pack(padx=10, pady=10)
self.button.pack(padx=10, pady=10)
def spawnthread(self):
self.button.config(state="disabled")
self.thread = ThreadedClient(self.queue)
self.thread.start()
self.periodiccall()
def periodiccall(self):
self.checkqueue()
if self.thread.is_alive():
self.after(100, self.periodiccall)
else:
self.button.config(state="active")
def checkqueue(self):
while self.queue.qsize():
try:
msg = self.queue.get(0)
self.listbox.insert('end', msg)
self.progressbar.step(25)
except Queue.Empty:
pass
class ThreadedClient(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
def run(self):
for x in range(1, 5):
time.sleep(2)
msg = "Function %s finished..." % x
self.queue.put(msg)
if __name__ == "__main__":
app = App()
app.mainloop()
Since the original example on ActiveState is a bit messy IMO (the ThreadedClient is quite coupled with the GuiPart, and things like controlling the moment to spawn the thread from the GUI are not as straightforward as they could be), I have refactored it and also added a Button to start the new thread.
To understand the 'freezing' you need to understand mainloop(). Calling this method starts the tkinter event loop. The main thread is responsible for this loop. Therefore, when your work intensive function runs in the main thread, it is also interfering with the mainloop. To prevent this you can use a secondary Thread to run your function. It's recommended that secondary threads are not given access to tkinter objects. Allen B.Taylor, author of mtTkinter, states:
The problems stem from the fact that the _tkinter module attempts to
gain control of the main thread via a polling technique when
processing calls from other threads.
If it succeeds, all is well. If it fails (i.e., after a timeout), the
application receives an exception with the message: "RuntimeError:
main thread is not in main loop".
You can have the secondary thread put information into a Queue. Then have a function that checks the Queue every x milliseconds, within the mainloop, via the after() method.
First, decide what you want the value of the Progressbar's maximum option to be.
This is the Progressbar's maximum indicator value (how many units are required to fill the Progressbar).
For example, you could set maximum=4 and then put the appropriate indicator value into the Queue after each of your four functions. The main thread can then retrieve these values (from the Queue) to set the progress via a tkinter.IntVar().
(Note that if you use progbar.step(), the Progressbar resets to 0 (empty) at the end, instead of reaching 4 (completely filled).)
Here's a quick look at how you can use a tkinter.IntVar() with a Progressbar:
int_var = tkinter.IntVar()
pb_instance = ttk.Progressbar(root, maximum=4)
pb_instance['variable'] = int_var
pb_instance.pack()
# completely fill the Progressbar
int_var.set(4)
# get the progress value
x = int_var.get()
Here's an example based on your own (renamed the "main" function "arbitrary"):
import time
import threading
try: import tkinter
except ImportError:
import Tkinter as tkinter
import ttk
import Queue as queue
else:
from tkinter import ttk
import queue
class GUI_Core(object):
def __init__(self):
self.root = tkinter.Tk()
self.int_var = tkinter.IntVar()
progbar = ttk.Progressbar(self.root, maximum=4)
# associate self.int_var with the progress value
progbar['variable'] = self.int_var
progbar.pack()
self.label = ttk.Label(self.root, text='0/4')
self.label.pack()
self.b_start = ttk.Button(self.root, text='Start')
self.b_start['command'] = self.start_thread
self.b_start.pack()
def start_thread(self):
self.b_start['state'] = 'disable'
self.int_var.set(0) # empty the Progressbar
self.label['text'] = '0/4'
# create then start a secondary thread to run arbitrary()
self.secondary_thread = threading.Thread(target=arbitrary)
self.secondary_thread.start()
# check the Queue in 50ms
self.root.after(50, self.check_que)
def check_que(self):
while True:
try: x = que.get_nowait()
except queue.Empty:
self.root.after(25, self.check_que)
break
else: # continue from the try suite
self.label['text'] = '{}/4'.format(x)
self.int_var.set(x)
if x == 4:
self.b_start['state'] = 'normal'
break
def func_a():
time.sleep(1) # simulate some work
def func_b():
time.sleep(0.3)
def func_c():
time.sleep(0.9)
def func_d():
time.sleep(0.6)
def arbitrary():
func_a()
que.put(1)
func_b()
que.put(2)
func_c()
que.put(3)
func_d()
que.put(4)
que = queue.Queue()
gui = GUI_Core() # see GUI_Core's __init__ method
gui.root.mainloop()
If all you want is something that indicates to the user that there is activity
you can set the Progressbar's mode option to 'indeterminate'.
The indicator bounces back and forth in this mode (the speed relates to the maximum option).
Then you can call the Progressbar's start() method directly before starting the secondary thread;
and then call stop() after secondary_thread.is_alive() returns False.
Here's an example:
import time
import threading
try: import tkinter
except ImportError:
import Tkinter as tkinter
import ttk
else: from tkinter import ttk
class GUI_Core(object):
def __init__(self):
self.root = tkinter.Tk()
self.progbar = ttk.Progressbar(self.root)
self.progbar.config(maximum=4, mode='indeterminate')
self.progbar.pack()
self.b_start = ttk.Button(self.root, text='Start')
self.b_start['command'] = self.start_thread
self.b_start.pack()
def start_thread(self):
self.b_start['state'] = 'disable'
self.progbar.start()
self.secondary_thread = threading.Thread(target=arbitrary)
self.secondary_thread.start()
self.root.after(50, self.check_thread)
def check_thread(self):
if self.secondary_thread.is_alive():
self.root.after(50, self.check_thread)
else:
self.progbar.stop()
self.b_start['state'] = 'normal'
def func_a():
time.sleep(1) # simulate some work
def func_b():
time.sleep(0.3)
def func_c():
time.sleep(0.9)
def func_d():
time.sleep(0.6)
def arbitrary():
func_a()
func_b()
func_c()
func_d()
gui = GUI_Core()
gui.root.mainloop()
→ Progressbar reference
You must be using:
self.pgBar.step(x)
where 'x' is the amount to be increased in progressbar.
for this to get updated in your UI you have to put
self.window.update_idletasks() after every self.pgBar.step(x) statement

Categories

Resources