Thông báo: Download 4 khóa học Python từ cơ bản đến nâng cao tại đây.
Cách hiển thị progress bar khi thread đang chạy trong Tkinter.
Trong bài viết này, bạn sẽ được hướng dẫn cách tích hợp và hiển thị thanh tiến trình (progress bar) trong ứng dụng Tkinter. Cụ thể, bạn sẽ tìm hiểu cách sử dụng luồng (thread) để thực hiện các tác vụ nền đồng thời cập nhật giao diện người dùng (UI) nhằm hiển thị tiến trình của tác vụ đó. Điều này sẽ giúp cải thiện trải nghiệm người dùng bằng cách cung cấp phản hồi trực quan khi ứng dụng đang xử lý các công việc kéo dài, như tải xuống dữ liệu hoặc thực hiện các tính toán phức tạp.
Giới thiệu hiển thị progress bar khi thread đang chạy trong Tkinter.
Hướng dẫn này giả định rằng bạn đã biết cách sử dụng phương thức after()
và hiểu cách hoạt động của luồng trong Python. Ngoài ra, bạn cũng nên biết cách chuyển đổi giữa các khung (frames) bằng phương thức tkraise()
.
Trong hướng dẫn này, chúng ta sẽ xây dựng một ứng dụng xem ảnh (picture viewer) hiển thị một hình ảnh ngẫu nhiên từ unsplash.com sử dụng API của nó.
Nếu bạn thực hiện yêu cầu HTTP tới endpoint API sau:
Bài viết này được đăng tại [free tuts .net]
https://source.unsplash.com/random/640x480
Bạn sẽ nhận được một hình ảnh ngẫu nhiên với kích thước 640×480.
Hình ảnh sau đây hiển thị ứng dụng Image Viewer cuối cùng:
Khi bạn nhấn vào nút "Next Picture", chương trình sẽ gọi API từ unsplash.com để tải xuống một hình ảnh ngẫu nhiên và hiển thị nó trên cửa sổ.
Nó cũng sẽ hiển thị một thanh tiến trình trong khi tải ảnh, chỉ ra rằng quá trình tải xuống đang diễn ra:
Để gọi API, bạn sử dụng mô-đun requests
.
Cách hiển thị progress bar khi thread đang chạy trong Tkinter.
Trước tiên, cài đặt mô-đun requests
nếu nó chưa có trên máy tính của bạn:
pip install requests
Thứ hai, định nghĩa một lớp mới kế thừa từ lớp Thread
:
class PictureDownload(Thread): def __init__(self, url): super().__init__() self.picture_file = None self.url = url def run(self): """Tải ảnh và lưu vào một file""" # Tải ảnh response = requests.get(self.url) picture_name = self.url.split('/')[-1] picture_file = f'./assets/{picture_name}.jpg' # Lưu ảnh vào file with open(picture_file, 'wb') as f: f.write(response.content) self.picture_file = picture_file
Thứ hai, định nghĩa một lớp mới kế thừa từ lớp Thread
:
class PictureDownload(Thread): def __init__(self, url): super().__init__() self.picture_file = None self.url = url def run(self): """Tải ảnh và lưu vào một file""" # Tải ảnh response = requests.get(self.url) picture_name = self.url.split('/')[-1] picture_file = f'./assets/{picture_name}.jpg' # Lưu ảnh vào with open(picture_file, 'wb') as f: f.write(response.content) self.picture_file = picture_file
Trong lớp PictureDownload
, phương thức run()
gọi API sử dụng mô-đun requests
.
Phương thức run()
tải xuống một hình ảnh và lưu nó vào thư mục /assets/
. Đồng thời, nó gán đường dẫn của ảnh đã tải xuống cho thuộc tính picture_file
của đối tượng.
Thứ ba, định nghĩa một lớp App
kế thừa từ lớp Tk
. Lớp App
đại diện cho cửa sổ gốc.
Cửa sổ gốc có hai khung, một để hiển thị thanh tiến trình (Progressbar) và một để hiển thị Canvas chứa hình ảnh đã tải xuống:
def __init__(self, canvas_width, canvas_height): super().__init__() self.resizable(0, 0) self.title('Image Viewer') # Khung tiến trình self.progress_frame = ttk.Frame(self) # Cấu hình lưới để đặt thanh tiến trình ở trung tâm self.progress_frame.columnconfigure(0, weight=1) self.progress_frame.rowconfigure(0, weight=1) # Thanh tiến trình 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) # Đặt khung tiến trình self.progress_frame.grid(row=0, column=0, sticky=tk.NSEW) # Khung hình ảnh self.picture_frame = ttk.Frame(self) # Chiều rộng và chiều cao của canvas 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)
Khi bạn nhấn nút "Next Picture", phương thức handle_download()
sẽ thực thi:
def handle_download(self): """Tải một bức ảnh ngẫu nhiên từ unsplash""" self.start_downloading() url = 'https://source.unsplash.com/random/640x480' download_thread = PictureDownload(url) download_thread.start() self.monitor(download_thread)
Phương thức handle_download()
hiển thị khung tiến trình bằng cách gọi phương thức start_downloading()
và bắt đầu thanh tiến trình:
def start_downloading(self): self.progress_frame.tkraise() self.pb.start(20)
Nó cũng tạo một luồng mới để tải xuống hình ảnh ngẫu nhiên và gọi phương thức monitor()
để giám sát trạng thái của luồng.
Dưới đây là phương thức monitor()
:
def monitor(self, download_thread): """Giám sát luồng tải xuống""" if download_thread.is_alive(): self.after(100, lambda: self.monitor(download_thread)) else: self.stop_downloading() self.set_picture(download_thread.picture_file)
Phương thức monitor()
kiểm tra trạng thái của luồng. Nếu luồng vẫn đang chạy, nó sẽ lên lịch kiểm tra lại sau 100ms.
Nếu không, phương thức monitor()
sẽ gọi stop_downloading()
để dừng thanh tiến trình, hiển thị khung hình ảnh, và hiển thị hình ảnh.
Dưới đây là phương thức stop_downloading()
:
def stop_downloading(self): self.picture_frame.tkraise() self.pb.stop()
Dưới đây là chương trình hoàn chỉnh của Image Viewer:
import requests import tkinter as tk from threading import Thread from PIL import Image, ImageTk from tkinter import ttk class PictureDownload(Thread): def __init__(self, url): super().__init__() self.picture_file = None self.url = url def run(self): """Tải ảnh và lưu vào tệp""" response = requests.get(self.url) picture_name = self.url.split('/')[-1] picture_file = f'./assets/{picture_name}.jpg' 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') self.progress_frame = ttk.Frame(self) self.progress_frame.columnconfigure(0, weight=1) self.progress_frame.rowconfigure(0, weight=1) 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) self.progress_frame.grid(row=0, column=0, sticky=tk.NSEW) self.picture_frame = ttk.Frame(self) self.canvas_width = canvas_width self.canvas_height = canvas_height 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) 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): """Đặt hình ảnh vào canvas""" pil_img = Image.open(file_path) resized_img = pil_img.resize( (self.canvas_width, self.canvas_height), Image.ANTIALIAS) self.img = ImageTk.PhotoImage(resized_img) self.bg = self.canvas.create_image( 0, 0, anchor=tk.NW, image=self.img) def handle_download(self): """Tải một bức ảnh ngẫu nhiên từ 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): """Giám sát luồng tải xuống""" 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()
Kết bài
Qua bài viết này, bạn đã học cách hiển thị thanh tiến trình trong ứng dụng Tkinter khi một luồng đang chạy. Bằng cách sử dụng luồng để xử lý tác vụ nền và cập nhật giao diện người dùng một cách đồng bộ, bạn có thể cải thiện hiệu suất ứng dụng và cung cấp trải nghiệm người dùng mượt mà hơn. Việc áp dụng các kỹ thuật này sẽ giúp bạn xây dựng các ứng dụng tương tác và thân thiện hơn, đồng thời tận dụng hiệu quả sức mạnh của Tkinter và lập trình đa luồng trong Python.