How to Display a Progress Bar while a Thread is Running in Tkinter
Summary: in this tutorial, you’ll learn to display a progressbar while a thread is running in a Tkinter application.
This tutorial assumes that you know how to use the after()
method and understand how threadings work in Python. Also, you should know how to switch between frames using the tkraise() method.
In this tutorial, you’ll build a picture viewer that shows a random picture from unsplash.com using its API.
If you make an HTTP request to the following API endpoint:
https://source.unsplash.com/random/640x480
Code language: Python (python)
…you’ll get a random picture with the size of 640×480.
The following picture shows the final Image Viewer application:
When you click the Next Picture button, the program calls the API from unsplash.com to download a random picture and displays it on the window.
It’ll also show a progress bar while the picture is downloading, indicating that the download is in progress:
To call the API, you use the requests
module.
First, install the requests
module if it’s not available on your computer:
pip install requests
Code language: Python (python)
Second, define a new class that inherits from the Thread
class:
class PictureDownload(Thread):
def __init__(self, url):
super().__init__() self.picture_file = None
self.url = url
def run(self):
""" download a picture and save it to a file """
# download the picture
response = requests.get(self.url, proxies=proxyDict)
picture_name = self.url.split('/')[-1]
picture_file = f'./assets/{picture_name}.jpg'
# save the picture to a file
with open(picture_file, 'wb') as f:
f.write(response.content)
self.picture_file = picture_file
Code language: Python (python)
In this PictureDownload
class, the run()
method calls the API using the requests
module.
The run()
method downloads a picture and saves it to the /assets/
folder. Also, it assigns the path of the downloaded picture to the picture_file
instance attribute.
Third, define an App
class that inherits the Tk
class. The App
class represents the root window.
The root
window has two frames, one for displaying the Progressbar and the other for showing the Canvas which holds the downloaded picture:
def __init__(self, canvas_width, canvas_height):
super().__init__()
self.resizable(0, 0)
self.title('Image Viewer') # Progress frame
self.progress_frame = ttk.Frame(self)
# configrue the grid to place the progress bar is at the center
self.progress_frame.columnconfigure(0, weight=1)
self.progress_frame.rowconfigure(0, weight=1)
# progressbar
self.pb = ttk.Progressbar(
self.progress_frame, orient=tk.HORIZONTAL, mode='indeterminate')
self.pb.grid(row=0, column=0, sticky=tk.EW, padx=10, pady=10)
# place the progress frame
self.progress_frame.grid(row=0, column=0, sticky=tk.NSEW)
# Picture frame
self.picture_frame = ttk.Frame(self)
# canvas width & height
self.canvas_width = canvas_width
self.canvas_height = canvas_height
# canvas
self.canvas = tk.Canvas(
self.picture_frame,
width=self.canvas_width,
height=self.canvas_height)
self.canvas.grid(row=0, column=0)
self.picture_frame.grid(row=0, column=0)
Code language: Python (python)
When you click the Next Picture
button, the handle_download()
method executes:
def handle_download(self):
""" Download a random photo from unsplash """
self.start_downloading() url = 'https://source.unsplash.com/random/640x480'
download_thread = PictureDownload(url)
download_thread.start()
self.monitor(download_thread)
Code language: Python (python)
The handle_download()
method shows the progress frame by calling the start_downloading()
method and starts the progress bar:
def start_downloading(self):
self.progress_frame.tkraise()
self.pb.start(20)
Code language: Python (python)
It also creates a new thread that downloads the random picture and calls the monitor()
method to monitor the status of the thread.
The following shows the monitor()
method:
def monitor(self, download_thread):
""" Monitor the download thread """
if download_thread.is_alive():
self.after(100, lambda: self.monitor(download_thread))
else:
self.stop_downloading()
self.set_picture(download_thread.picture_file)
Code language: Python (python)
The monitor()
method checks the status of the thread. If the thread is running, it schedules another check after 100ms.
Otherwise, the monitor()
method calls the stop_downloading()
method to stop the progressbar, display the picture frame, and show the image.
The following shows the stop_downloading()
method:
def stop_downloading(self):
self.picture_frame.tkraise()
self.pb.stop()
Code language: Python (python)
The following shows the complete Image Viewer program:
import requests
import tkinter as tk
from threading import Thread
from PIL import Image, ImageTk
from tkinter import ttk
from proxies import proxyDictclass PictureDownload(Thread):
def __init__(self, url):
super().__init__()
self.picture_file = None
self.url = url
def run(self):
""" download a picture and save it to a file """
# download the picture
response = requests.get(self.url, proxies=proxyDict)
picture_name = self.url.split('/')[-1]
picture_file = f'./assets/{picture_name}.jpg'
# save the picture to a file
with open(picture_file, 'wb') as f:
f.write(response.content)
self.picture_file = picture_file
class App(tk.Tk):
def __init__(self, canvas_width, canvas_height):
super().__init__()
self.resizable(0, 0)
self.title('Image Viewer')
# Progress frame
self.progress_frame = ttk.Frame(self)
# configrue the grid to place the progress bar is at the center
self.progress_frame.columnconfigure(0, weight=1)
self.progress_frame.rowconfigure(0, weight=1)
# progressbar
self.pb = ttk.Progressbar(
self.progress_frame, orient=tk.HORIZONTAL, mode='indeterminate')
self.pb.grid(row=0, column=0, sticky=tk.EW, padx=10, pady=10)
# place the progress frame
self.progress_frame.grid(row=0, column=0, sticky=tk.NSEW)
# Picture frame
self.picture_frame = ttk.Frame(self)
# canvas width & height
self.canvas_width = canvas_width
self.canvas_height = canvas_height
# canvas
self.canvas = tk.Canvas(
self.picture_frame,
width=self.canvas_width,
height=self.canvas_height)
self.canvas.grid(row=0, column=0)
self.picture_frame.grid(row=0, column=0)
# Button
btn = ttk.Button(self, text='Next Picture')
btn['command'] = self.handle_download
btn.grid(row=1, column=0)
def start_downloading(self):
self.progress_frame.tkraise()
self.pb.start(20)
def stop_downloading(self):
self.picture_frame.tkraise()
self.pb.stop()
def set_picture(self, file_path):
""" Set the picture to the canvas """
pil_img = Image.open(file_path)
# resize the picture
resized_img = pil_img.resize(
(self.canvas_width, self.canvas_height),
Image.ANTIALIAS)
self.img = ImageTk.PhotoImage(resized_img)
# set background image
self.bg = self.canvas.create_image(
0,
0,
anchor=tk.NW,
image=self.img)
def handle_download(self):
""" Download a random photo from unsplash """
self.start_downloading()
url = 'https://source.unsplash.com/random/640x480'
download_thread = PictureDownload(url)
download_thread.start()
self.monitor(download_thread)
def monitor(self, download_thread):
""" Monitor the download thread """
if download_thread.is_alive():
self.after(100, lambda: self.monitor(download_thread))
else:
self.stop_downloading()
self.set_picture(download_thread.picture_file)
if __name__ == '__main__':
app = App(640, 480)
app.mainloop()
Code language: Python (python)
In this tutorial, you’ve learned how to display a progressbar that connects to a running thread to indicate that an operation is still in progress.