You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Tkinter图片下载GUI应用中创建线程避免界面未响应?

Fix Tkinter GUI "Not Responding" During Image Downloads with Threading

Hey there! The issue you're hitting is super common with Tkinter: it runs on a single main thread, so any long-running blocking task (like downloading images) will freeze the GUI because it blocks the event loop that handles button clicks, window updates, etc. Let's fix this by offloading the download work to a separate thread, while keeping all GUI interactions safely in the main thread.

Here's how to adjust your code step by step:

Key Changes Needed

  1. Move download logic to a dedicated thread function: This keeps the main thread free to handle GUI interactions.
  2. Use Tkinter's after() method for GUI updates: You can't modify Tkinter widgets directly from a child thread—after() lets you schedule GUI updates to run in the main thread safely.

Modified Working Code

from tkinter import *
import requests
import os
from threading import Thread
from urllib.request import urlretrieve

class Gui(Tk):
    def __init__(self):
        self.do_splash_call = False
        Tk.__init__(self)
        self.title('unsplash image download')
        self.geometry('900x500')
        # main canvas
        main = Canvas(self, bg='#0099e6')
        main.place(relx=0, rely=0, relwidth=1, relheight=1)
        # frame1 for upper left
        name_frame = Frame(main)
        name_frame.place(relx=.1, rely=.1)
        # name input and label
        name_label = Label(name_frame, text='Image Name:')
        name_label.pack(side=LEFT, pady=3)
        self.name = Entry(name_frame, bd=0, font=2, width=23)
        self.name.pack(side=RIGHT, pady=3)
        # frame2 for upper right
        page_frame = Frame(main)
        page_frame.place(relx=.5, rely=.1, relwidth=.3)
        # landing page
        Label(page_frame, text='Landing page:').pack(side=LEFT, pady=3)
        self.page = Spinbox(page_frame, from_=1, to=50, width=30)
        self.page.pack(side=RIGHT, pady=3)
        # frame3 for down left
        size_frame = Frame(main)
        size_frame.place(relx=.1, rely=.2)
        # name input and label
        size_label = Label(size_frame, text='Android, pc, tablet:')
        size_label.pack(side=LEFT, pady=3)
        self.size = Entry(size_frame, bd=0, font=2)
        self.size.pack(side=RIGHT, pady=3)
        # button
        download = Button(main, bd=1, font=4, text='Download', width=10, command=self.splash)
        download.place(relx=.6, rely=.2)
        # output
        self.output = Text(main, bd=2)
        self.output.place(relx=0, rely=.5, relheight=1, relwidth=1)
        # path to save images
        save_frame = Frame(main)
        save_frame.place(relx=.1, rely=.3, relwidth=.7)
        Label(save_frame, text='Save:').pack(side=LEFT, padx=5)
        self.save = Entry(save_frame, bd=1)
        self.save.place(relx=.1, relwidth=1)
        # insert path for save images
        self.save.insert(0, str('c:\\users\\abc\\Desktop\\photos'))

    def path(self):
        save_path = self.save.get()
        if not os.path.exists(save_path):
            os.makedirs(save_path)
        os.chdir(save_path)

    def update_output(self, message, is_error=False):
        # Helper to safely update Text widget from main thread
        def _safe_update():
            self.output.insert(INSERT, f'\n{message}')
            if is_error:
                self.output.tag_add('error', 'end-2l', 'end-1l')
                self.output.tag_config('error', background='#ff4d4d', foreground='black')
            else:
                self.output.tag_add('fine', 'end-2l', 'end-1l')
                self.output.tag_config('fine', background='lightGreen', foreground='#196619')
            # Auto-scroll to latest message
            self.output.see(END)
        # Schedule update to run in main thread
        self.after(0, _safe_update)

    def download_images(self):
        # This runs in a separate background thread
        try:
            # Get user inputs
            image_name = self.name.get().strip()
            pages = self.page.get()
            size_input = self.size.get().strip().lower()

            # Set image size/orientation based on input
            if size_input in ['pc', 'desktop']:
                size = 1080
                orientation = 'landscape'
            elif size_input in ['hd', 'full hd', 'clear', 'normal']:
                size = 1500
                orientation = 'landscape'
            elif size_input in ['android', 'mini', 'mobile']:
                size = 400
                orientation = 'portrait'
            else:
                size = 1500
                orientation = 'landscape'

            # Fixed API URL (removed HTML entities & and extra spaces)
            api = f'https://api.unsplash.com/photos/search?query={image_name}&resolution={size}&orientation={orientation}&client_id=YOUR_UNSPLASH_API_KEY&page={pages}&w=1500&dpi=2'
            res = requests.get(api).json()

            # Handle API errors (like invalid key)
            if 'errors' in res:
                self.update_output(f"API Error: {','.join(res['errors'])}", is_error=True)
                return

            # Prepare save path
            self.path()

            # Download first 2 images
            for idx, photo in enumerate(res[:2]):
                url = photo['links']['download']
                name_of_image = photo.get('alt_description', f'image_{idx+1}')
                img_name = '_'.join(name_of_image[:40].split(' ')) + '.png'
                
                self.update_output(f'Downloading img.... {img_name}')
                urlretrieve(url, img_name)
                self.update_output(f'Successfully downloaded: {img_name}')

        except Exception as e:
            self.update_output(f'Error: {str(e)}', is_error=True)
            print(e)

    def splash(self):
        # Start the download thread (daemon=True means it exits when GUI closes)
        download_thread = Thread(target=self.download_images, daemon=True)
        download_thread.start()

if __name__ == '__main__':
    app = Gui()
    app.mainloop()

What's Different?

  • download_images() method: All the download logic lives here, running in a separate daemon thread. Daemon threads automatically terminate when the main GUI window closes, which is perfect for background tasks.
  • update_output() helper: Uses self.after(0, ...) to safely update the Text widget from the main thread—Tkinter isn't thread-safe for widget modifications, so this is non-negotiable.
  • Fixed API URL: Replaced & with & (HTML entities don't belong in raw URLs) and cleaned up extra spaces to avoid API errors.
  • Simplified splash(): Now it just starts the download thread instead of running blocking code directly, keeping the GUI responsive.

Important Reminders

  • Replace YOUR_UNSPLASH_API_KEY with your actual Unsplash API key.
  • Never modify Tkinter widgets directly from child threads—always use after() to schedule updates in the main thread.
  • The daemon thread ensures your download task won't hang around if you close the GUI window early.

内容的提问来源于stack exchange,提问作者Rohit Dalal

火山引擎 最新活动