Sharing my experiments with TQDM and TK, working off of the answers from the lovely @xmcp and @runDOSrun
Bad example
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()
Basic working example
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.
Threaded example
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()
Multiple progress bars
Complicated example
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.