Giao diện - Tkinter - Thread
Summary: in this tutorial, you’ll learn how to use multiple threads in Tkinter applications to make the applications more responsive.
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 requests
Code language: Python (python)
Next, import tkinter
, threading
, and requests
modules:
import tkinter as tk from tkinter import ttk from tkinter.messagebox import showerror from threading import Thread import requests
Code 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.text
Code language: Python (python)
How the AsyncDownload
class works:
- In the
__init__()
method of theAsyncDownload
class, we initialize thehtml
andurl
attributes. - In the
run()
method, we call the get theget()
function to download the webpage specified by the URL and assign the HTML source code to thehtml
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.