I made a GUI Python program with Tkinter.
I made a progressbar using "from tkinter.ttk import Progressbar".
When I press a button, I apply a function that do something and return a Boolean value.
I need that the progressbar will run until that function ends the process, and after that it will stop.
from tkinter.ttk import Progressbar
import time
import threading
wheel_flag = False
root = tk.Tk()
wheel = Progressbar(row,orient=HORIZONTAL,length=100,mode="indeterminate")
def btn_function()
loading_function = threading.Thread(target=start_loading)
loading_function.start()
boolean_wheel = threading.Thread(target=some_function, args = (x,y)) #"some_function" returns a boolean value
boolean_wheel.start()
while True:
if not boolean_wheel.is_alive():
break
wheel_flag = True
def start_loading():
while True:
global wheel_flag
wheel['value'] += 10
root.update_idletasks()
time.sleep(0.5)
if wheel_flag:
break
Here I don't get the boolean_wheel value, but I want to check if it true or false and send to the user message if function succeeded or not.
I want that when "btn_function" is applied, the progressbarwill start to load until "some_function" will finish run.
Now the result I get is that the loading start only after "some_function" finished, and runs without stopping until I close the program.
I would use root.after(millisecond, function) instead of while True-loop to run function which updates progressbar every few seconds. And I would use directly boolean_wheel.is_alive() in this function to stop updateing.
import tkinter as tk
from tkinter import ttk
import time
import threading
# --- functions ---
def some_function(x, y):
time.sleep(10)
def btn_function():
x = 100
y = 100
boolean_wheel = threading.Thread(target=some_function, args=(x,y))
boolean_wheel.start()
# block button
button['state'] = 'disabled'
# show progressbar
wheel.grid(row=0, column=1)
# start updating progressbar
update_progressbar(boolean_wheel) # send thread as parameter - so it doesn't need `global`
def update_progressbar(thread):
if thread.is_alive():
# update progressbar
wheel['value'] += 10
# check again after 250ms (0.25s)
root.after(250, update_progressbar, thread)
else:
# hide progressbar
wheel.grid_forget()
# unblock button
button['state'] = 'normal'
# --- main ---
root = tk.Tk()
wheel = ttk.Progressbar(root, orient='horizontal', length=100, mode="indeterminate")
#wheel.grid(row=0, column=1) # don't show
button = tk.Button(root, text='Start', command=btn_function)
button.grid(row=0, column=0)
root.mainloop()
EDIT:
If you would send widgets to functions as parameters then you could run it for many progressbars
import tkinter as tk
from tkinter import ttk
import time
import threading
# --- functions ---
def some_function(x, y):
time.sleep(10)
def btn_function(row, button, progressbar):
x = 100
y = 100
boolean_wheel = threading.Thread(target=some_function, args=(x,y))
boolean_wheel.start()
start_loading(row, button, progressbar, boolean_wheel)
def start_loading(row, button, progressbar, thread):
# block button
button['state'] = 'disabled'
# show progressbar
progressbar.grid(row=row, column=1)
# start updating progressbar
update_progressbar(button, progressbar, thread) # send thread as parameter - so it doesn't need `global`
def update_progressbar(button, progressbar, thread):
if thread.is_alive():
# update progressbar
progressbar['value'] += 10
# check again after 250ms (0.25s)
root.after(250, update_progressbar, button, progressbar, thread)
else:
end_loading(button, progressbar)
def end_loading(button, progressbar):
# hide progressbar
progressbar.grid_forget()
# unblock button
button['state'] = 'normal'
# --- main ---
root = tk.Tk()
# ---
wheel1 = ttk.Progressbar(root, orient='horizontal', length=100, mode="indeterminate")
#wheel.grid(row=1, column=1) # don't show
button1 = tk.Button(root, text='Start')
button1.grid(row=1, column=0)
button1['command'] = lambda:btn_function(1, button1, wheel1) # need to assign after creating button - to send button to function
# ---
wheel2 = ttk.Progressbar(root, orient='horizontal', length=100, mode="indeterminate")
#wheel2.grid(row=2, column=1) # don't show
button2 = tk.Button(root, text='Start')
button2.grid(row=2, column=0)
button2['command'] = lambda:btn_function(2, button2, wheel2) # need to assign after creating button - to send button to function
# ---
root.mainloop()
I would even put it in Frame and create own widget class MyProgress(tk.Frame) to create many widgets using for-loop
import tkinter as tk
from tkinter import ttk
import time
import threading
# --- classes ---
class MyProgrees(tk.Frame):
def __init__(self, master, target_function, target_args, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.target_function = target_function
self.target_args = target_args
self.progressbar = ttk.Progressbar(self, orient='horizontal', length=100, mode="indeterminate")
#self.progressbar.grid(row=0, column=1) # don't show
self.button = tk.Button(self, text='Start', command=self.on_press_button)
self.button.grid(row=0, column=0)
def on_press_button(self):
self.thread = threading.Thread(target=self.target_function, args=self.target_args)
self.thread.start()
self.start_loading()
def start_loading(self):
# block button
self.button['state'] = 'disabled'
# show progressbar
self.progressbar.grid(row=0, column=1)
# start updating progressbar
self.update_progressbar()
def update_progressbar(self):
if self.thread.is_alive():
# update progressbar
self.progressbar['value'] += 10
# check again after 250ms (0.25s)
self.after(250, self.update_progressbar)
else:
self.end_loading()
def end_loading(self):
# hide progressbar
self.progressbar.grid_forget()
# unblock button
self.button['state'] = 'normal'
# --- functions ---
def some_function(x, y):
time.sleep(10)
# --- main ---
root = tk.Tk()
# ---
x = 100
y = 100
for _ in range(10):
widget = MyProgrees(root, some_function, (x, y))
widget.pack(fill='x', expand=True)
# ---
root.mainloop()
the proper solution is to remove the loop in btn_function and instead have some_function set wheel_flag after it is done.
def some_function_wrapper(*args,**kwargs):
global wheel_flag
some_function(*args,**kwargs)
wheel_flag = True
and call this instead of some_function inside your thread.
now another solution is to just run the entire btn_function inside another thread, by adding
command= lambda : threading.Thread(target=btn_function).start()
inside your button definition, but nesting threads sometimes becomes hard to track.
Edited:
It works.
I checked the function value inside that function
def some_function_wrapper(*args,**kwargs):
global wheel_flag
check = some_function(*args,**kwargs)
if check:
print("works")
else:
print("don't work")
wheel_flag = True
Related
I'm writing a program with Python's tkinter library.
My major problem is that I don't know how to create a timer or a clock like hh:mm:ss.
I need it to update itself (that's what I don't know how to do); when I use time.sleep() in a loop the whole GUI freezes.
Tkinter root windows have a method called after which can be used to schedule a function to be called after a given period of time. If that function itself calls after you've set up an automatically recurring event.
Here is a working example:
# for python 3.x use 'tkinter' rather than 'Tkinter'
import Tkinter as tk
import time
class App():
def __init__(self):
self.root = tk.Tk()
self.label = tk.Label(text="")
self.label.pack()
self.update_clock()
self.root.mainloop()
def update_clock(self):
now = time.strftime("%H:%M:%S")
self.label.configure(text=now)
self.root.after(1000, self.update_clock)
app=App()
Bear in mind that after doesn't guarantee the function will run exactly on time. It only schedules the job to be run after a given amount of time. It the app is busy there may be a delay before it is called since Tkinter is single-threaded. The delay is typically measured in microseconds.
Python3 clock example using the frame.after() rather than the top level application. Also shows updating the label with a StringVar()
#!/usr/bin/env python3
# Display UTC.
# started with https://docs.python.org/3.4/library/tkinter.html#module-tkinter
import tkinter as tk
import time
def current_iso8601():
"""Get current date and time in ISO8601"""
# https://en.wikipedia.org/wiki/ISO_8601
# https://xkcd.com/1179/
return time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
def createWidgets(self):
self.now = tk.StringVar()
self.time = tk.Label(self, font=('Helvetica', 24))
self.time.pack(side="top")
self.time["textvariable"] = self.now
self.QUIT = tk.Button(self, text="QUIT", fg="red",
command=root.destroy)
self.QUIT.pack(side="bottom")
# initial time display
self.onUpdate()
def onUpdate(self):
# update displayed time
self.now.set(current_iso8601())
# schedule timer to call myself after 1 second
self.after(1000, self.onUpdate)
root = tk.Tk()
app = Application(master=root)
root.mainloop()
from tkinter import *
import time
tk=Tk()
def clock():
t=time.strftime('%I:%M:%S',time.localtime())
if t!='':
label1.config(text=t,font='times 25')
tk.after(100,clock)
label1=Label(tk,justify='center')
label1.pack()
clock()
tk.mainloop()
You should call .after_idle(callback) before the mainloop and .after(ms, callback) at the end of the callback function.
Example:
import tkinter as tk
import time
def refresh_clock():
clock_label.config(
text=time.strftime("%H:%M:%S", time.localtime())
)
root.after(1000, refresh_clock) # <--
root = tk.Tk()
clock_label = tk.Label(root, font="Times 25", justify="center")
clock_label.pack()
root.after_idle(refresh_clock) # <--
root.mainloop()
I have a simple answer to this problem. I created a thread to update the time. In the thread i run a while loop which gets the time and update it. Check the below code and do not forget to mark it as right answer.
from tkinter import *
from tkinter import *
import _thread
import time
def update():
while True:
t=time.strftime('%I:%M:%S',time.localtime())
time_label['text'] = t
win = Tk()
win.geometry('200x200')
time_label = Label(win, text='0:0:0', font=('',15))
time_label.pack()
_thread.start_new_thread(update,())
win.mainloop()
I just created a simple timer using the MVP pattern (however it may be
overkill for that simple project). It has quit, start/pause and a stop button. Time is displayed in HH:MM:SS format. Time counting is implemented using a thread that is running several times a second and the difference between the time the timer has started and the current time.
Source code on github
from tkinter import *
from tkinter import messagebox
root = Tk()
root.geometry("400x400")
root.resizable(0, 0)
root.title("Timer")
seconds = 21
def timer():
global seconds
if seconds > 0:
seconds = seconds - 1
mins = seconds // 60
m = str(mins)
if mins < 10:
m = '0' + str(mins)
se = seconds - (mins * 60)
s = str(se)
if se < 10:
s = '0' + str(se)
time.set(m + ':' + s)
timer_display.config(textvariable=time)
# call this function again in 1,000 milliseconds
root.after(1000, timer)
elif seconds == 0:
messagebox.showinfo('Message', 'Time is completed')
root.quit()
frames = Frame(root, width=500, height=500)
frames.pack()
time = StringVar()
timer_display = Label(root, font=('Trebuchet MS', 30, 'bold'))
timer_display.place(x=145, y=100)
timer() # start the timer
root.mainloop()
You can emulate time.sleep with tksleep and call the function after a given amount of time. This may adds readability to your code, but has its limitations:
def tick():
while True:
clock.configure(text=time.strftime("%H:%M:%S"))
tksleep(0.25) #sleep for 0.25 seconds
root = tk.Tk()
clock = tk.Label(root,text='5')
clock.pack(fill=tk.BOTH,expand=True)
tick()
root.mainloop()
I'm writing a program in tkinter using Progressbar. But there is a problem when I added stop function it doesn't work. When I press "stop" button nothing happens, it should stop loading progressbar. I use Python version 3.8. The code below:
from tkinter import *
from tkinter import ttk
import time
root = Tk()
def run():
pb['maximum']=100
for i in range(101):
time.sleep(0.05)
pb['value']=i
pb.update()
def stop():
pb.stop()
runbutt = Button(root,text="Runprogr",command=run)
runbutt.pack()
stopbutt = Button(root,text="Stopbut",command=stop)
stopbutt.pack()
pb = ttk.Progressbar(root,length=300,orient="horizontal")
pb.pack()
root.geometry("300x300")
root.mainloop()
The cause is that pb.stop couldn't stop the function in run.it will also increase by itself.
You could use .after(ms, callback) to add the value(then you no longer need to use time.sleep()).
If you want to stop it,use .after_cancel():
from tkinter import *
from tkinter import ttk
import time
root = Tk()
root.add_value = None
def run():
def add():
if pb['value'] >= 100:
return
pb['value'] += 1
root.add_value = root.after(50, add)
if root.add_value: # to prevent increasing the speed when user pressed "Runprogr" many times.
return
root.add_value = root.after(50, add)
def stop():
if not root.add_value: # to prevent raising Exception when user pressed "Stopbut" button many times.
return
root.after_cancel(root.add_value)
root.add_value = None
runbutt = Button(root, text="Runprogr", command=run)
runbutt.pack()
stopbutt = Button(root, text="Stopbut", command=stop)
stopbutt.pack()
pb = ttk.Progressbar(root, length=300, orient="horizontal")
pb.pack()
root.geometry("300x300")
root.mainloop()
I'm writing a program with Python's tkinter library.
My major problem is that I don't know how to create a timer or a clock like hh:mm:ss.
I need it to update itself (that's what I don't know how to do); when I use time.sleep() in a loop the whole GUI freezes.
Tkinter root windows have a method called after which can be used to schedule a function to be called after a given period of time. If that function itself calls after you've set up an automatically recurring event.
Here is a working example:
# for python 3.x use 'tkinter' rather than 'Tkinter'
import Tkinter as tk
import time
class App():
def __init__(self):
self.root = tk.Tk()
self.label = tk.Label(text="")
self.label.pack()
self.update_clock()
self.root.mainloop()
def update_clock(self):
now = time.strftime("%H:%M:%S")
self.label.configure(text=now)
self.root.after(1000, self.update_clock)
app=App()
Bear in mind that after doesn't guarantee the function will run exactly on time. It only schedules the job to be run after a given amount of time. It the app is busy there may be a delay before it is called since Tkinter is single-threaded. The delay is typically measured in microseconds.
Python3 clock example using the frame.after() rather than the top level application. Also shows updating the label with a StringVar()
#!/usr/bin/env python3
# Display UTC.
# started with https://docs.python.org/3.4/library/tkinter.html#module-tkinter
import tkinter as tk
import time
def current_iso8601():
"""Get current date and time in ISO8601"""
# https://en.wikipedia.org/wiki/ISO_8601
# https://xkcd.com/1179/
return time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
def createWidgets(self):
self.now = tk.StringVar()
self.time = tk.Label(self, font=('Helvetica', 24))
self.time.pack(side="top")
self.time["textvariable"] = self.now
self.QUIT = tk.Button(self, text="QUIT", fg="red",
command=root.destroy)
self.QUIT.pack(side="bottom")
# initial time display
self.onUpdate()
def onUpdate(self):
# update displayed time
self.now.set(current_iso8601())
# schedule timer to call myself after 1 second
self.after(1000, self.onUpdate)
root = tk.Tk()
app = Application(master=root)
root.mainloop()
from tkinter import *
import time
tk=Tk()
def clock():
t=time.strftime('%I:%M:%S',time.localtime())
if t!='':
label1.config(text=t,font='times 25')
tk.after(100,clock)
label1=Label(tk,justify='center')
label1.pack()
clock()
tk.mainloop()
You should call .after_idle(callback) before the mainloop and .after(ms, callback) at the end of the callback function.
Example:
import tkinter as tk
import time
def refresh_clock():
clock_label.config(
text=time.strftime("%H:%M:%S", time.localtime())
)
root.after(1000, refresh_clock) # <--
root = tk.Tk()
clock_label = tk.Label(root, font="Times 25", justify="center")
clock_label.pack()
root.after_idle(refresh_clock) # <--
root.mainloop()
I have a simple answer to this problem. I created a thread to update the time. In the thread i run a while loop which gets the time and update it. Check the below code and do not forget to mark it as right answer.
from tkinter import *
from tkinter import *
import _thread
import time
def update():
while True:
t=time.strftime('%I:%M:%S',time.localtime())
time_label['text'] = t
win = Tk()
win.geometry('200x200')
time_label = Label(win, text='0:0:0', font=('',15))
time_label.pack()
_thread.start_new_thread(update,())
win.mainloop()
I just created a simple timer using the MVP pattern (however it may be
overkill for that simple project). It has quit, start/pause and a stop button. Time is displayed in HH:MM:SS format. Time counting is implemented using a thread that is running several times a second and the difference between the time the timer has started and the current time.
Source code on github
from tkinter import *
from tkinter import messagebox
root = Tk()
root.geometry("400x400")
root.resizable(0, 0)
root.title("Timer")
seconds = 21
def timer():
global seconds
if seconds > 0:
seconds = seconds - 1
mins = seconds // 60
m = str(mins)
if mins < 10:
m = '0' + str(mins)
se = seconds - (mins * 60)
s = str(se)
if se < 10:
s = '0' + str(se)
time.set(m + ':' + s)
timer_display.config(textvariable=time)
# call this function again in 1,000 milliseconds
root.after(1000, timer)
elif seconds == 0:
messagebox.showinfo('Message', 'Time is completed')
root.quit()
frames = Frame(root, width=500, height=500)
frames.pack()
time = StringVar()
timer_display = Label(root, font=('Trebuchet MS', 30, 'bold'))
timer_display.place(x=145, y=100)
timer() # start the timer
root.mainloop()
You can emulate time.sleep with tksleep and call the function after a given amount of time. This may adds readability to your code, but has its limitations:
def tick():
while True:
clock.configure(text=time.strftime("%H:%M:%S"))
tksleep(0.25) #sleep for 0.25 seconds
root = tk.Tk()
clock = tk.Label(root,text='5')
clock.pack(fill=tk.BOTH,expand=True)
tick()
root.mainloop()
I am totally new in python GUI and Tkinter. Now i want an entry field where i can change the value or time of self.hide when i will execute this code. that means self.hide value will change from Entry field. In this code this value is statically set to 1 minute. need help from experts.
import Tkinter as Tk
import time
import tkMessageBox
class Window:
def __init__(self):
self.root = None
self.hide = 1 #minutes
self.show = 3 #seconds
def close(self):
self.root.destroy()
return
def new(self):
self.root = Tk.Tk()
self.root.overrideredirect(True)
self.root.geometry("{0}x{1}+0+0".format(self.root.winfo_screenwidth(), self.root.winfo_screenheight()))
self.root.configure(bg='black')
Tk.Label(self.root, text='Hello', fg='white', bg='black', font=('Helvetica', 30)).place(anchor='center', relx=0.5, rely=0.5)
#tkMessageBox.showinfo("Notification", "Your time is up. Time to do next job. . .")
Tk.Button(text = 'Close', command = self.close).pack()
self.root.after(self.show*1000, self.pp)
def pp(self):
if self.root:
self.root.destroy()
time.sleep(self.hide*60)
self.new()
self.root.mainloop()
return
Window().pp()
Try This. It may help you.
from Tkinter import *
import time
root = Tk()
def close():
root.destroy()
def show():
root.deiconify()
button.config(text = 'Close', command = close)
root.after(1000, hide)
def hide():
root.withdraw()
time_to_sleep = set_time_to_sleep.get()
time_to_sleep = float(time_to_sleep)
#print time_to_sleep
time.sleep(time_to_sleep)
show()
set_time_to_sleep = Entry(root)
set_time_to_sleep.pack(side=LEFT)
button = Button(text = 'Set Time', command = hide)
button.pack()
root.mainloop()
To summarise:
Instead of using the sleep function, use the after function. This will not freeze the GUI.
Set the "wait" time of the after function self.Entry.get(). This will collect the info you have put into the Entry.
For more info, look at these links. People smarter than myself give a very clear explication on how to use the functions.
Tkinter, executing functions over time
tkinter: how to use after method
I'm trying to get a progress bar to appear on a Toplevel widget, then incrementally increase every few seconds until complete.
When I click "Start", there is delay of a few seconds before the progressbar widget appears. When it does appear, the progress bar does not increment at all.
Here is what I've tried so far:
class MainUI:
def __init__(self, parent):
self.parent = parent
self.counter = IntVar()
self.main_container = Frame(self.parent)
self.main_container.pack()
self.btn_start = Button(self.main_container, command=self.btn_start_click)
self.btn_start.configure(
text="Start", background="Grey",
padx=50
)
self.btn_start.pack(side=LEFT)
def progress_bar(self):
self.pbar_top = Toplevel(self.main_container)
self.download_label = Label(
self.pbar_top,
text="Download Bar"
)
self.download_label.pack(side=TOP)
self.download_bar = ttk.Progressbar(
self.pbar_top, orient="horizontal",
length=400, mode="determinate",
variable=self.counter, maximum=5
)
self.download_bar.pack(side=TOP)
def btn_start_click(self):
self.progress_bar()
for i in range(4):
self.counter = i
time.sleep(1)
root = Tk()
root.title("Progress Bar Test")
main_ui = MainUI(root)
root.mainloop()
I found that commenting out the for loop inside btn_start_click causes the progress bar to appear immediately after clicking "Start". However, as before the actual bar does not increment.
Could someone please point out what I'm doing wrong?
The problem is that you are calling time.sleep(1) in the same thread than your Tkinter code. It makes your GUI unresponsive until the task (in this case, the call to btn_start_click) finishes. To solve this, you can start a new thread which executes that function, and update the progress bar in the GUI thread by using a synchronized object like Queue. This is a working example I wrote for a similar question.
Besides, you should call self.counter.set(i) instead of self.counter = i to update the value of the IntVar. Another solution more explicit is self.download_bar.step() with the appropiate increment.
from tkinter import *
from tkinter import ttk
class MainUI:
def __init__(self, parent):
self.parent = parent
self.counter = IntVar()
self.main_container = Frame(self.parent)
self.main_container.pack()
self.btn_start = Button(self.main_container, command=self.startThread)
self.btn_start.configure(
text="Start", background="Grey",
padx=50
)
self.btn_start.pack(side=LEFT)
def progress_bar(self):
self.pbar_top = Toplevel(self.main_container)
self.download_label = Label(
self.pbar_top,
text="Download Bar"
)
self.download_label.pack(side=TOP)
self.download_bar = ttk.Progressbar(
self.pbar_top, orient="horizontal",
length=400, mode="determinate",
variable=self.counter, maximum=5
)
self.download_bar.pack(side=TOP)
def startThread(self):
import threading
def btn_start_click():
self.progress_bar()
for i in range(6):
self.counter.set(i)
import time
time.sleep(1)
t = threading.Thread(None, btn_start_click, ())
t.start()
root = Tk()
root.title("Progress Bar Test")
main_ui = MainUI(root)
root.mainloop()