How to Display a Progress Bar while a Thread is Running in Tkinter

2024. 4. 2. 17:53GUI/tkinter

요약 : 이 튜토리얼에서는 Tkinter 애플리케이션에서 스레드가 실행되는 동안 진행률 표시줄을 표시하는 방법을 배웁니다.

이 튜토리얼에서는 여러분이 after()메서드를 사용하는 방법을 알고 있고 Python에서 스레딩이 작동하는 방식을 이해하고 있다고 가정합니다. 또한 tkraise() 메서드를 사용하여 프레임 간을 전환하는 방법을 알아야 합니다 .

이 튜토리얼에서는 API를 사용하여 unsplash.com의 임의의 사진을 표시하는 사진 뷰어를 구축합니다.

다음 API 엔드포인트에 HTTP 요청을 하는 경우:

 
 

...640×480 크기의 무작위 사진을 얻게 됩니다.

다음 그림은 최종 이미지 뷰어 애플리케이션을 보여줍니다.

 

 

다음 사진 버튼을 클릭하면 프로그램이 unsplash.com에서 API를 호출하여 임의의 사진을 다운로드하여 창에 표시합니다.

또한 사진을 다운로드하는 동안 진행률 표시줄이 표시 되어 다운로드가 진행 중임을 나타냅니다.

 

 

API를 호출하려면 requests모듈을 사용합니다 .

먼저 requests컴퓨터에서 모듈을 사용할 수 없는 경우 모듈을 설치하십시오.

 
pip install requests
 
Requirement already satisfied: requests in /usr/local/anaconda3/lib/python3.11/site-packages (2.31.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/anaconda3/lib/python3.11/site-packages (from requests) (2.0.4)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/anaconda3/lib/python3.11/site-packages (from requests) (3.4)
Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/anaconda3/lib/python3.11/site-packages (from requests) (2.0.7)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/anaconda3/lib/python3.11/site-packages (from requests) (2024.2.2)
Note: you may need to restart the kernel to use updated packages.
 

둘째, Thread 클래스에서 상속되는 새 클래스를 정의합니다.

 
from threading import 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() 메서드는 requests 모듈을 사용하여 API를 호출합니다.

이 run() 메서드는 사진을 다운로드하여 /assets/ 폴더에 저장합니다. 또한 다운로드한 그림의 경로를 picture_file 인스턴스 속성에 할당합니다.

셋째, Tk 클래스를 상속하는 App 클래스를 정의합니다. App 클래스는 루트 창을 나타냅니다.

root 창에는 두 개의 프레임이 있는데, 하나는 Progressbar를 표시하기 위한 것이고 다른 하나는 다운로드한 그림이 들어 있는 캔버스를 표시하기 위한 것입니다.

 
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 &amp; 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)
 

Next Picture버튼을 클릭하면 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()메서드는 스레드의 상태를 확인합니다. 스레드가 실행 중이면 100ms 후에 또 다른 확인을 예약합니다.

그렇지 않은 경우 monitor() 메서드는 진행률 표시줄을 중지하고 그림 프레임을 표시하며 이미지를 표시하는 stop_downloading() 메서드를 호출합니다.

다음은 stop_downloading()방법을 보여줍니다.

 
def stop_downloading(self):
    self.picture_frame.tkraise()
    self.pb.stop()   
 
!pip install proxies
!pip install requests
 
Requirement already satisfied: proxies in /usr/local/anaconda3/lib/python3.11/site-packages (1.6)
Requirement already satisfied: requests in /usr/local/anaconda3/lib/python3.11/site-packages (from proxies) (2.31.0)
Requirement already satisfied: pyquery in /usr/local/anaconda3/lib/python3.11/site-packages (from proxies) (2.0.0)
Requirement already satisfied: gevent in /usr/local/anaconda3/lib/python3.11/site-packages (from proxies) (24.2.1)
Requirement already satisfied: zope.event in /usr/local/anaconda3/lib/python3.11/site-packages (from gevent->proxies) (5.0)
Requirement already satisfied: zope.interface in /usr/local/anaconda3/lib/python3.11/site-packages (from gevent->proxies) (5.4.0)
Requirement already satisfied: greenlet>=3.0rc3 in /usr/local/anaconda3/lib/python3.11/site-packages (from gevent->proxies) (3.0.1)
Requirement already satisfied: lxml>=2.1 in /usr/local/anaconda3/lib/python3.11/site-packages (from pyquery->proxies) (4.9.3)
Requirement already satisfied: cssselect>=1.2.0 in /usr/local/anaconda3/lib/python3.11/site-packages (from pyquery->proxies) (1.2.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/anaconda3/lib/python3.11/site-packages (from requests->proxies) (2.0.4)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/anaconda3/lib/python3.11/site-packages (from requests->proxies) (3.4)
Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/anaconda3/lib/python3.11/site-packages (from requests->proxies) (2.0.7)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/anaconda3/lib/python3.11/site-packages (from requests->proxies) (2024.2.2)
Requirement already satisfied: setuptools in /usr/local/anaconda3/lib/python3.11/site-packages (from zope.event->gevent->proxies) (68.2.2)
Requirement already satisfied: requests in /usr/local/anaconda3/lib/python3.11/site-packages (2.31.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/anaconda3/lib/python3.11/site-packages (from requests) (2.0.4)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/anaconda3/lib/python3.11/site-packages (from requests) (3.4)
Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/anaconda3/lib/python3.11/site-packages (from requests) (2.0.7)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/anaconda3/lib/python3.11/site-packages (from requests) (2024.2.2)
 

# ModuleNotFoundError: No module named 'proxies' 에러가 나오면 위 함수를 실행하세요.

 

다음은 전체 이미지 뷰어 프로그램을 보여줍니다.

 
import requests
import tkinter as tk
from threading import Thread
from PIL import Image, ImageTk
from tkinter import ttk
import proxies   # 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(url, proxies=proxies)   # self.   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 &amp; 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()
 
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[28], line 6
      4 from PIL import Image, ImageTk
      5 from tkinter import ttk
----> 6 import proxies   # from proxies import proxyDict
      9 class PictureDownload(Thread):
     10     def __init__(self, url):

ModuleNotFoundError: No module named 'proxies'
 

이 자습서에서는 작업이 아직 진행 중임을 나타내기 위해 실행 중인 스레드에 연결하는 진행률 표시줄을 표시하는 방법을 배웠습니다.

 

위와 같은 에러를 해결하여 주시길 바랍니다.

 

 

'GUI > tkinter' 카테고리의 다른 글

Tkinter Validation  (0) 2024.04.04
Tkinter MVC  (0) 2024.04.03
How to Schedule an Action with Tkinter after() method  (0) 2024.04.02
Tkinter Thread  (0) 2024.03.31
How to Change the Appearances of Widgets Dynamically Using Ttk Style map() Method  (0) 2024.03.30