Tkinter Thread

When to use Thread in Tkinter applications

In a Tkinter application, the main loop should always start in the main thread. It’s responsible for handling events and updating the GUI.

If you have a background operation that takes time, you should execute it in a separate thread.

Otherwise, the application won’t be responsive. In the worst case, it will freeze while the operation is running.

To create and control multiple threads in Tkinter applications, you can use the Python threading module.

The threading module is included in Python’s standard library so you don’t need to install it.

For more information on how to use the threading module, you can follow the Python threading tutorial.

Tkinter thread example

We’ll build a simple program that downloads a webpage specified by an URL and displays its contents in a Text widget:

To download a webpage, we’ll use the requests module.

First, install the requests module by executing the following command:

pip install requestsCode language: Python (python)

Next, import tkinterthreading, and requests modules:

import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showerror
from threading import Thread
import requestsCode language: Python (python)

Then, define a new class called AsyncDownload that inherits from the Thread class:

class AsyncDownload(Thread):
    def __init__(self, url):
        super().__init__()
        self.html = None
        self.url = url

    def run(self):
        response = requests.get(self.url)
        self.html = response.textCode language: Python (python)

How the AsyncDownload class works:

  • In the __init__() method of the AsyncDownload class, we initialize the html and url attributes.
  • In the run() method, we call the get the get() function to download the webpage specified by the URL and assign the HTML source code to the html attribute.

After that, create the App class inherits from the Tk class. The App class represents the root window.

The root window consists of three frames that hold all the widgets. We won’t focus on how to create widgets and place them on the window using the grid geometry manager.

When you click the download button, the program executes the handle_download() method of the App class.

In the handle_download() method, we check if the url is provided. If yes, we create a new instance of the AsyncDownload class and start the thread. Also, we disable the download button and clear the contents of the Text widget.

In addition, we call the monitor() method to monitor the status of the thread.

def handle_download(self):
    url = self.url_var.get()
    if url:
        self.download_button['state'] = tk.DISABLED
        self.html.delete(1.0, "end")

        download_thread = AsyncDownload(url)
        download_thread.start()

        self.monitor(download_thread)
    else:
        showerror(title='Error',
                message='Please enter the URL of the webpage.')
Code language: Python (python)

In the monitor() method, we schedule an action that will run the monitor() method after 100ms if the thread is still alive.

If the download completed, we update the contents for the Entry widget and re-enable the download button:

def monitor(self, thread):
    if thread.is_alive():
        # check the thread every 100ms
        self.after(100, lambda: self.monitor(thread))
    else:
        self.html.insert(1.0, thread.html)
        self.download_button['state'] = tk.NORMAL
Code language: Python (python)

Finally, run the application’s main loop:

if __name__ == "__main__":
    app = App()
    app.mainloop()Code language: Python (python)

The following show the complete program:

import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showerror
from threading import Thread
import requests


class AsyncDownload(Thread):
    def __init__(self, url):
        super().__init__()

        self.html = None
        self.url = url

    def run(self):
        response = requests.get(self.url)
        self.html = response.text


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.title('Webpage Download')
        self.geometry('680x430')
        self.resizable(0, 0)

        self.create_header_frame()
        self.create_body_frame()
        self.create_footer_frame()

    def create_header_frame(self):

        self.header = ttk.Frame(self)
        # configure the grid
        self.header.columnconfigure(0, weight=1)
        self.header.columnconfigure(1, weight=10)
        self.header.columnconfigure(2, weight=1)
        # label
        self.label = ttk.Label(self.header, text='URL')
        self.label.grid(column=0, row=0, sticky=tk.W)

        # entry
        self.url_var = tk.StringVar()
        self.url_entry = ttk.Entry(self.header,
                                   textvariable=self.url_var,
                                   width=80)

        self.url_entry.grid(column=1, row=0, sticky=tk.EW)

        # download button
        self.download_button = ttk.Button(self.header, text='Download')
        self.download_button['command'] = self.handle_download
        self.download_button.grid(column=2, row=0, sticky=tk.E)

        # attach the header frame
        self.header.grid(column=0, row=0, sticky=tk.NSEW, padx=10, pady=10)

    def handle_download(self):
        url = self.url_var.get()
        if url:
            self.download_button['state'] = tk.DISABLED
            self.html.delete(1.0, "end")

            download_thread = AsyncDownload(url)
            download_thread.start()

            self.monitor(download_thread)
        else:
            showerror(title='Error',
                      message='Please enter the URL of the webpage.')

    def monitor(self, thread):
        if thread.is_alive():
            # check the thread every 100ms
            self.after(100, lambda: self.monitor(thread))
        else:
            self.html.insert(1.0, thread.html)
            self.download_button['state'] = tk.NORMAL

    def create_body_frame(self):
        self.body = ttk.Frame(self)
        # text and scrollbar
        self.html = tk.Text(self.body, height=20)
        self.html.grid(column=0, row=1)

        scrollbar = ttk.Scrollbar(self.body,
                                  orient='vertical',
                                  command=self.html.yview)

        scrollbar.grid(column=1, row=1, sticky=tk.NS)
        self.html['yscrollcommand'] = scrollbar.set

        # attach the body frame
        self.body.grid(column=0, row=1, sticky=tk.NSEW, padx=10, pady=10)

    def create_footer_frame(self):
        self.footer = ttk.Frame(self)
        # configure the grid
        self.footer.columnconfigure(0, weight=1)
        # exit button
        self.exit_button = ttk.Button(self.footer,
                                      text='Exit',
                                      command=self.destroy)

        self.exit_button.grid(column=0, row=0, sticky=tk.E)

        # attach the footer frame
        self.footer.grid(column=0, row=2, sticky=tk.NSEW, padx=10, pady=10)


if __name__ == "__main__":
    app = App()
    app.mainloop()Code language: Python (python)

Summary

  • Do execute background tasks in separate threads to make the Tkinter application responsive.
Follow Us On