Подключение индикатора процесса Progressbar к потоку в Tkinter

В этом уроке вы научитесь отображать индикатор прогресса (progressbar) во время выполнения потока в приложении Tkinter в Python.

Предполагается, что вы знаете, как использовать метод after() и понимаете, как работают потоки в Python. Кроме того, вы должны знать, как переключаться между фреймами с помощью метода tkraise().

Мы создадим средство просмотра изображений, которое будет показывать случайную картинку с сайта unsplash.com, используя его API.

Если вы сделаете HTTP-запрос к следующей конечной точке API:

https://source.unsplash.com/random/640x480

…вы получите случайную картинку размером 640×480.

На следующем рисунке показано окончательное приложение Image Viewer:

Приложение Image Viewer

При нажатии кнопки «Следующее изображение» программа вызывает API с unsplash.com для загрузки случайного изображения и отображает его в окне.

Во время загрузки изображения также будет отображаться индикатор выполнения, указывающий на то, что загрузка идет:

progressbar показывает, что загрузка идет

Для вызова API используется модуль requests.

  • Сначала установите модуль requests, если он недоступен на вашем компьютере:
pip install requests
  • Во-вторых, определите новый класс, который наследуется от класса Thread:
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

В этом классе PictureDownload метод run() вызывает API с помощью модуля requests.

Метод run() загружает картинку и сохраняет ее в папке /assets/. Также он назначает путь к загруженной картинке атрибуту экземпляра picture_file.

  • В-третьих, определите класс App, который наследует класс Tk. Класс App представляет корневое окно.

Корневое окно имеет два фрейма: один для отображения полосы прогресса, а другой — для отображения холста, содержащего загруженное изображение:

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)

При нажатии кнопки «Следующее изображение» выполняется метод handle_download():

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)

Метод handle_download() отображает фрейм прогресса, вызывая метод start_downloading(), и запускает индикатор прогресса:

def start_downloading(self):
    self.progress_frame.tkraise()
    self.pb.start(20)

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

Ниже показан метод monitor():

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)

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

В противном случае метод monitor() вызывает метод stop_downloading(), чтобы остановить индикатор выполнения, отобразить рамку изображения и показать изображение.

Ниже показан метод stop_downloading():

def stop_downloading(self):
    self.picture_frame.tkraise()
    self.pb.stop()

Ниже показана полная версия программы Image Viewer:

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

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