How to Create Downloading Progress Bar in Ttk

How to create downloading progress bar in ttk?

For determinate mode you do not want to call start. Instead, simply configure the value of the widget or call the step method.

If you know in advance how many bytes you are going to download (and I assume you do since you're using determinate mode), the simplest thing to do is set the maxvalue option to the number you are going to read. Then, each time you read a chunk you configure the value to be the total number of bytes read. The progress bar will then figure out the percentage.

Here's a simulation to give you a rough idea:

import tkinter as tk
from tkinter import ttk

class SampleApp(tk.Tk):

def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.button = ttk.Button(text="start", command=self.start)
self.button.pack()
self.progress = ttk.Progressbar(self, orient="horizontal",
length=200, mode="determinate")
self.progress.pack()

self.bytes = 0
self.maxbytes = 0

def start(self):
self.progress["value"] = 0
self.maxbytes = 50000
self.progress["maximum"] = 50000
self.read_bytes()

def read_bytes(self):
'''simulate reading 500 bytes; update progress bar'''
self.bytes += 500
self.progress["value"] = self.bytes
if self.bytes < self.maxbytes:
# read more bytes after 100 ms
self.after(100, self.read_bytes)

app = SampleApp()
app.mainloop()

For this to work you're going to need to make sure you don't block the GUI thread. That means either you read in chunks (like in the example) or do the reading in a separate thread. If you use threads you will not be able to directly call the progressbar methods because tkinter is single threaded.

You might find the progressbar example on tkdocs.com to be useful.

Ttk Indeterminate progress bar on button press

Heh welcome to the fun world of event driven programming :). You can't use join here, the point of that function is to block until the thread is done and the whole point of using a thread is to avoid blocking the mainloop. You have 2 choices: either set up the GUI to constantly poll the thread to see if it's still running, or set up the thread to send a message back to the GUI when it's done. This latter option is probably the cleanest, and it's often done using tkinter's event mechanism.

from tkinter import *
from tkinter import ttk
import threading
import time

def generate_reports(start, end):
print("provide a mcve next time!")
time.sleep(5)

def run_report(root, *args):
generate_reports(*args)
root.event_generate("<<PhishDoneEvent>>") # yes, this is using tkinter in a thread, but some tkinter methods are ok to use in threads

class BillingApp:
def __init__(self, root):
self.mainframe = ttk.Frame(root, padding = '4 4 12 12')
self.mainframe.grid(column = 0, row = 0, sticky = (N, W, E, S))
ttk.Button(self.mainframe, text = "Generate Billing Reports", command = self.do_reports).grid(column = 2, row = 3, sticky = (W, E))
root.bind("<<PhishDoneEvent>>", self.report_done)

def do_reports(self, *args):
# note this makes a new widget with every click ... this is bad. Refactor to reuse the widget.
self.pbar = ttk.Progressbar(self.mainframe, orient = HORIZONTAL, mode = 'indeterminate')
self.pbar.grid(row = 4, column = 3, sticky = (W, E))
start, end = 4,5
t1 = threading.Thread(target = run_report, args = [root, start, end])
t1.start()
self.pbar.start()

def report_done(self, event=None):
self.pbar.stop()
Label(self.mainframe, text="report done").grid(row = 4, column = 3)

root = Tk()
BillingApp(root)
root.mainloop()

Download progressbar for Python 3

There is urlretrieve() that downloads an url to a file and allows to specify a reporthook callback to report progess:

#!/usr/bin/env python3
import sys
from urllib.request import urlretrieve

def reporthook(blocknum, blocksize, totalsize):
readsofar = blocknum * blocksize
if totalsize > 0:
percent = readsofar * 1e2 / totalsize
s = "\r%5.1f%% %*d / %d" % (
percent, len(str(totalsize)), readsofar, totalsize)
sys.stderr.write(s)
if readsofar >= totalsize: # near the end
sys.stderr.write("\n")
else: # total size is unknown
sys.stderr.write("read %d\n" % (readsofar,))

urlretrieve(url, 'downloaded_file.py', reporthook)

Here's a GUI progress bar:

import sys
from threading import Event, Thread
from tkinter import Tk, ttk
from urllib.request import urlretrieve

def download(url, filename):
root = progressbar = quit_id = None
ready = Event()
def reporthook(blocknum, blocksize, totalsize):
nonlocal quit_id
if blocknum == 0: # started downloading
def guiloop():
nonlocal root, progressbar
root = Tk()
root.withdraw() # hide
progressbar = ttk.Progressbar(root, length=400)
progressbar.grid()
# show progress bar if the download takes more than .5 seconds
root.after(500, root.deiconify)
ready.set() # gui is ready
root.mainloop()
Thread(target=guiloop).start()
ready.wait(1) # wait until gui is ready
percent = blocknum * blocksize * 1e2 / totalsize # assume totalsize > 0
if quit_id is None:
root.title('%%%.0f %s' % (percent, filename,))
progressbar['value'] = percent # report progress
if percent >= 100: # finishing download
quit_id = root.after(0, root.destroy) # close GUI

return urlretrieve(url, filename, reporthook)

download(url, 'downloaded_file.py')

On Python 3.3 urlretrieve() has different reporthook interface (see issue 16409). To workaround it, you could access the previous interface via FancyURLopener:

from urllib.request import FancyURLopener
urlretrieve = FancyURLopener().retrieve

To update the progress bar within the same thread, you could inline urlretrieve() code:

from tkinter import Tk, ttk
from urllib.request import urlopen

def download2(url, filename):
response = urlopen(url)
totalsize = int(response.headers['Content-Length']) # assume correct header
outputfile = open(filename, 'wb')

def download_chunk(readsofar=0, chunksize=1 << 13):
# report progress
percent = readsofar * 1e2 / totalsize # assume totalsize > 0
root.title('%%%.0f %s' % (percent, filename,))
progressbar['value'] = percent

# download chunk
data = response.read(chunksize)
if not data: # finished downloading
outputfile.close()
root.destroy() # close GUI
else:
outputfile.write(data) # save to filename
# schedule to download the next chunk
root.after(0, download_chunk, readsofar + len(data), chunksize)

# setup GUI to show progress
root = Tk()
root.withdraw() # hide
progressbar = ttk.Progressbar(root, length=400)
progressbar.grid()
# show progress bar if the download takes more than .5 seconds
root.after(500, root.deiconify)
root.after(0, download_chunk)
root.mainloop()

download2(url, 'downloaded_file.py')

how to display the progress bar of a function in an APP(GUI using tkinter)

It is better to run a time-consuming task in a thread, otherwise it will make tkinter application not responding.

I use another function start_classification() to be executed after menu item Classify is selected.

Inside that function:

  • create a progress bar in indeterminate mode
  • start the progress bar animation
  • create a tkinter variable to hold the result from Classifyall()
  • start a child thread to execute Classifyall()
  • wait for the child thread to complete
  • destroy the progress bar
  • update the text box with the result from Classifyall()
import threading
from tkinter import ttk
...

# added argument var (a tkinter StringVar)
def Classifyall(var):
table = BeautifulTable()
table.column_headers = ["File Name", "File Format"]
directory_path = '/home/.../All files/*'
file_list = glob.glob(directory_path)
for file in file_list:
filepath, file_extention = os.path.splitext(file)
filename = filepath.rsplit('/', 1)[1]
table.append_row([filename, file_extention])

# note that it is not safe to update tkinter widget in a child thread
# so use the passed tkinter variable to save the result instead
var.set(table)

def start_classification():
# create a progress bar and start the animation
pbar = ttk.Progressbar(gui1, orient='horizontal', length=300, mode='indeterminate')
pbar.place(relx=0.5, rely=0.5, anchor='c')
pbar.start()

var = StringVar() # hold the result from Classifyall()
# execute Classifyall() in a child thread
threading.Thread(target=Classifyall, args=(var,)).start()
# wait for the child thread to complete
tx.wait_variable(var)

pbar.destroy()
tx.insert(END, var.get())

...

tool_menu = Menu(menu)
tool_menu.add_command(label='Classify', command=start_classification) # call start_classification() instead
menu.add_cascade(label='Tool', menu=tool_menu)

...

Update Tkinter progress bar

In event driven programming (GUIs) you can't have a blocking loop like your for loop. You have to set an event using after to run a function again. This marries well with iterators:

from Tkinter import *
import ttk
import numpy as np

root = Tk()

files = iter(np.arange(1,10000))

downloaded = IntVar()
def loading():
try:
downloaded.set(next(files)) # update the progress bar
root.after(1, loading) # call this function again in 1 millisecond
except StopIteration:
# the files iterator is exhausted
root.destroy()

progress= ttk.Progressbar(root, orient = 'horizontal', maximum = 10000, variable=downloaded, mode = 'determinate')
progress.pack(fill=BOTH)
start = ttk.Button(root,text='start',command=loading)
start.pack(fill=BOTH)

root.mainloop()


Related Topics



Leave a reply



Submit