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/640x480Code 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 requestsCode 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 proxyDict


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


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.

Did you find this tutorial helpful ?