Как использовать модуль threading в приложениях Tkinter в Python

В приложении Tkinter основной цикл всегда должен начинаться в основном потоке. Он отвечает за обработку событий и обновление GUI.

Если у вас есть фоновая операция, которая требует времени, ее следует выполнить в отдельном потоке. В противном случае приложение не будет реагировать. В худшем случае оно зависнет во время выполнения операции.

Когда использовать Thread в приложениях Tkinter

Для создания и управления несколькими потоками в приложениях Tkinter можно использовать threading — модуль потоковой обработки в Python.

Модуль threading данных включен в стандартную библиотеку Python, поэтому вам не нужно его устанавливать. Дополнительную информацию об использовании модуля потоков можно найти в руководстве по работе с потоками в Python.

Пример Tkinter thread

Мы создадим простую программу, которая загружает веб-страницу, указанную по URL-адресу, и отображает ее содержимое в виджете Text:

Tkinter thread

Для загрузки веб-страницы мы воспользуемся модулем запросов requests.

Сначала установите модуль requests, выполнив следующую команду:

pip install requests

Далее импортируем модули tkinter, threading и requests:

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

Затем определите новый класс с именем AsyncDownload, который наследует от класса Thread:

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

Как работает класс AsyncDownload:

  • В методе __init__() класса AsyncDownload мы инициализируем атрибуты html и url.
  • В методе run() мы вызываем функцию get() для загрузки веб-страницы, указанной в URL-адресе, и назначаем исходный HTML-код атрибуту html.

После этого создайте класс App, унаследованный от класса Tk. Класс App представляет корневое окно.

Корневое окно состоит из трех фреймов, которые содержат все виджеты. Мы не будем фокусироваться на том, как создавать виджеты и размещать их в окне с помощью менеджера геометрии сетки.

При нажатии кнопки загрузки программа выполняет метод handle_download() класса App.

В методе handle_download() мы проверяем, предоставлен ли URL. Если да, мы создаем новый экземпляр класса AsyncDownload и запускаем поток. Также мы отключаем кнопку загрузки и очищаем содержимое виджета Text.

Кроме того, мы вызываем метод monitor() для отслеживания состояния потока.

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.')

В методе monitor() мы планируем действие, которое запустит метод monitor() через 100 мс, если поток все еще активен.

Если загрузка завершена, мы обновляем содержимое виджета Entry и снова включаем кнопку загрузки:

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

Наконец, запустим основной цикл приложения:

if __name__ == "__main__":
    app = App()
    app.mainloop()

Ниже представлена полная программа:

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()
Похожие посты
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *