Подключение индикатора процесса Progressbar к потоку в Tkinter
В этом уроке вы научитесь отображать индикатор прогресса (progressbar) во время выполнения потока в приложении Tkinter в Python.
Предполагается, что вы знаете, как использовать метод after() и понимаете, как работают потоки в Python. Кроме того, вы должны знать, как переключаться между фреймами с помощью метода tkraise().
Мы создадим средство просмотра изображений, которое будет показывать случайную картинку с сайта unsplash.com, используя его API.
Если вы сделаете HTTP-запрос к следующей конечной точке API:
https://source.unsplash.com/random/640x480
…вы получите случайную картинку размером 640×480.
На следующем рисунке показано окончательное приложение Image Viewer:
При нажатии кнопки «Следующее изображение» программа вызывает API с unsplash.com для загрузки случайного изображения и отображает его в окне.
Во время загрузки изображения также будет отображаться индикатор выполнения, указывающий на то, что загрузка идет:
Для вызова 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()