Reputation: 349
I have a simple Tk GUI and a long process in a function attached to a button. I want a progress bar when I click on the button, just like it starts a long process.
How can I do that? This is my current code:
from tkinter import Button, Tk, HORIZONTAL
from tkinter.ttk import Progressbar
import time
class MonApp(Tk):
def __init__(self):
super().__init__()
bt1 = Button(self, text='Traitement', command=self.traitement)
bt1.grid()
self.progress = Progressbar(self, orient=HORIZONTAL, length=100, mode='indeterminate')
self.progress.grid()
self.progress.grid_forget()
def traitement(self):
self.progress.grid()
self.progress.start()
time.sleep(15)
## Just like you have many, many code lines...
self.progress.stop()
if __name__ == '__main__':
app = MonApp()
app.mainloop()
How can I put a progress bar in that application?
Upvotes: 23
Views: 76437
Reputation: 1
use this code " self.progressbar = ttk.Progressbar(root, value=0, maximum=100, orient=tk.HORIZONTAL, length=190, mode='determinate') self.progressbar.grid(row=4 + i, column=2, padx=padding)" change the length or change the mode its up to you
Upvotes: 0
Reputation: 1
Try this -
from tkinter import *
from tkinter import ttk
import requests
from tqdm.tk import tqdm
from functools import partial
root = Tk()
root.geometry("600x500")
root.minsize(145, 50)
root.maxsize(900, 600)
frm = ttk.Frame(root)
frm.grid()
def down():
# progress = Progressbar(root, orient=HORIZONTAL, length=100, mode='determinate')
r = requests.get("https://www.win-rar.com/fileadmin/winrar versions/winrar/winrar-x64-621.exe", stream=True)
totalbyte = int(r.headers['Content-Length'])
print(totalbyte)
byte = 0
progress_bar = tqdm(total=totalbyte, unit='B', unit_scale=True, tk_parent=root)
with open("win.rar", "wb") as f:
for chunk in r.iter_content(chunk_size=120):
progress_bar.update(120)
f.write(chunk)
byte += 120
root.update()
progress_bar.close()
progress_bar._tk_window.destroy()
print(byte)
ttk.Label(frm, text="Hello World!").grid(column=0, row=0)
ttk.Button(frm, text="Download win.rar file",
command=partial(down)).grid(column=1, row=0)
ttk.Button(frm, text="Quit", command=root.destroy).grid(column=2, row=0)
root.mainloop()
Upvotes: 0
Reputation: 11
The progress bar is created and finished in one method - the result is the program seems to hang until a completed progress bar pops up.
from tqdm.tk import tqdm
from time import sleep
from tkinter import Tk, Button
window = Tk()
def run_task():
pbar = tqdm(total=30, tk_parent=window)
for _ in range(30):
sleep(0.1)
pbar.update(1)
start_button = Button(window, text="Start", command=run_task)
start_button.pack()
window.mainloop()
Also added lines to disable the button when you press it, and re-enable at the end. If you leave those two lines off, pressing the button again restarts the progress bar.
from tqdm.tk import tqdm
from time import sleep
from tkinter import Tk, Button
window = Tk()
pbar = tqdm(tk_parent=window) # Create the progress bar ahead of time
pbar._tk_window.withdraw() # Hide it immediately
def run_task():
start_button['state'] = 'disabled'
pbar._tk_window.deiconify()
pbar.reset(total=30)
for _ in range(30):
sleep(0.1)
pbar.update(1)
pbar._tk_window.withdraw() # don't close the progress bar, just hide it for next time
# pbar.close() # intended usage, might be buggy
# pbar._tk_window.destroy() # workaround
start_button['state'] = 'normal'
start_button = Button(window, text="Start", command=run_task)
start_button.pack()
window.mainloop()
A single progress bar; the button is disabled If you leave off the button-disabling step, pressing the button again resets the progress bar.
This one doesn't make you wait for the progress bar to finish. Button is still active, so you can spawn multiple progress bars if you spam it.
from tqdm.tk import tqdm
from time import sleep
from tkinter import Tk, Button
import threading
window = Tk()
def run_task():
def threaded_task():
pbar = tqdm(iterable=range(30), total=30, tk_parent=window)
for _ in pbar:
sleep(0.1)
# pbar.update(1)
# pbar.close() # intended usage, might be buggy
pbar._tk_window.destroy() # workaround
threading.Thread(target=threaded_task).start()
start_button = Button(window, text="Start", command=run_task)
start_button.pack()
window.mainloop()
Uses a ThreadPoolExecutor to spawn several progress bars at once. The main bar should stick to the foreground, and will update as the subprocesses complete.
import random
from tqdm.tk import tqdm
from time import sleep
from tkinter import Tk, Button
import threading
from concurrent.futures import ThreadPoolExecutor
window = Tk()
# pbar = tqdm(total=30, tk_parent=window)
# pbar._tk_window.withdraw()
# pbar._tk_window.deiconify()
def run_task():
def threaded_task(iterable: []):
def inner_task(n: int):
pbar = tqdm(iterable=range(n), total=n, tk_parent=window, desc=threading.current_thread().name, grab=False)
for _ in pbar:
sleep(.1)
# pbar.close()
pbar._tk_window.destroy()
with ThreadPoolExecutor(max_workers=8) as tpe:
pbar = tqdm(tpe.map(inner_task, iterable), total=len(iterable), grab=True, desc="Main progress bar")
pbar._tk_window.attributes('-topmost', True) # Keep the main progress bar on top, or it's hard to see
pbar._tk_window.focus_get()
list(pbar)
# Map makes an iterable, but you have to iterate on it to actually show the progress bar
# list is a quick and easy iteration.
# pbar.close()
pbar._tk_window.destroy()
threading.Thread(target=threaded_task, kwargs={'iterable': random.sample(range(1, 100), 20)}).start()
start_button = Button(window, text="Start", command=run_task)
start_button.pack()
window.mainloop()
Many progress bars running concurrently
For usability, you're almost certainly going to want to use the threaded example(s) - the basic working example (re)uses one pre-defined progress bar and is a little more clunky. With the threaded examples, you don't have to know in advance how many progress bars you're going to need.
You can absolutely disable the button on the threaded examples too, to prevent multiple concurrent activations.
Upvotes: 1
Reputation: 10985
tqdm is a popular progressbar library that also has experimental support for tkinter (link to API). It is effectively a wrapper for ttk.Progressbar
.
The usage is not well documented (and there are obvious bugs) but here is a minimal working example:
from tqdm.tk import tqdm
from time import sleep
from tkinter import Tk, Button
window = Tk()
pbar = tqdm(total=30, tk_parent=window)
def run_task():
for _ in range(30):
sleep(0.1)
pbar.update(1)
pbar.close() # intended usage, might be buggy
#pbar._tk_window.destroy() # workaround
start_button = Button(window, text="Start", command=run_task)
start_button.pack()
window.mainloop()
Will produce a progressbar in a separate window looking like this:
Upvotes: 2
Reputation: 3742
You can find ttk.Progressbar
at tkdocs
import time
from tkinter import *
from tkinter.ttk import *
tk = Tk()
progress = Progressbar(tk, orient=HORIZONTAL, length=100, mode='determinate')
def bar():
progress['value'] = 20
tk.update_idletasks()
time.sleep(1)
progress['value'] = 50
tk.update_idletasks()
time.sleep(1)
progress['value'] = 80
tk.update_idletasks()
time.sleep(1)
progress['value'] = 100
progress.pack()
Button(tk, text='foo', command=bar).pack()
mainloop()
It's better to use threading
and run your code in another thread.
Like this:
import threading
import time
from tkinter import Button, Tk, HORIZONTAL
from tkinter.ttk import Progressbar
class MonApp(Tk):
def __init__(self):
super().__init__()
self.btn = Button(self, text='Traitement', command=self.traitement)
self.btn.grid(row=0, column=0)
self.progress = Progressbar(self, orient=HORIZONTAL, length=100, mode='indeterminate')
def traitement(self):
def real_traitement():
self.progress.grid(row=1,column=0)
self.progress.start()
time.sleep(5)
self.progress.stop()
self.progress.grid_forget()
self.btn['state']='normal'
self.btn['state']='disabled'
threading.Thread(target=real_traitement).start()
if __name__ == '__main__':
app = MonApp()
app.mainloop()
Upvotes: 33
Reputation: 185
For all the GUI elements to modify themselves (in your case, for the progress bar to move) the execution must hit app.mainloop()
.
In your case, def traitement(self):
starts and then stops the progressbar without hitting the mainloop, so it fails to visibly reflect the intended progressbar movement on the GUI. The catch here is, when the execution hits mainloop, progressbar is configured to 'stop' state.
Hence, it is a good idea to execute time consuming activities on a different Thread as shown by @xmcp
However, if you DO NOT want to use threading, you can use the after method to achieve what you want:
def stop_progressbar(self):
self.progress.stop()
def traitement(self):
self.progress.grid()
self.progress.start()
self.after(15000, self.stop_progressbar)
## Call Just like you have many, many code lines...
The above code used self.after() method which will execute the stop_progressbar method to stop after 15 seconds, instead of time.sleep() which blocks the mainthread.
Upvotes: 3