You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kivy GUI按钮触发下载代码时界面阻塞问题求助

Fixing Kivy GUI Freeze During Image Downloads with Progress Updates

Hey there! Let's tackle that GUI freeze issue you're facing. The core problem here is that your download logic (including pyppeteer operations) is running directly on Kivy's main UI thread, which blocks all interactive elements like window resizing or button clicks until the task finishes. Here's a step-by-step solution using threads and Kivy's Clock to keep your UI responsive while showing download progress:

Step 1: Update Your GUI Code (gui.py)

We'll move the download task to a background thread and use Kivy's Clock to safely update the UI from the main thread:

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.clock import Clock
from kivy.uix.popup import Popup
from kivy.uix.label import Label
import threading
import asyncio
from download import Downloader

class MainLayout(FloatLayout):
    def on_download(self):
        # Reset progress bar and validate input URL
        self.ids.progress_bar.value = 0
        url = self.ids.url_input.text
        if not url.strip():
            self.show_error("Please enter a valid URL!")
            return

        downloader = Downloader()
        # Launch download task in a background daemon thread
        threading.Thread(
            target=self._run_download_task,
            args=(downloader, url),
            daemon=True
        ).start()
    
    def _run_download_task(self, downloader, url):
        # Run async pyppeteer logic in a separate asyncio loop (sub-thread)
        try:
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            loop.run_until_complete(downloader.download_images(url, self.update_progress))
            # Notify UI when download finishes
            Clock.schedule_once(lambda dt: self.show_download_complete())
        except Exception as e:
            # Pass errors back to UI thread
            Clock.schedule_once(lambda dt: self.show_error(str(e)))
    
    def update_progress(self, progress):
        # Schedule UI update on main thread (required for Kivy widgets)
        Clock.schedule_once(lambda dt: self._update_progress_ui(progress))
    
    def _update_progress_ui(self, progress):
        self.ids.progress_bar.value = progress
    
    def show_download_complete(self):
        popup = Popup(
            title="Success!",
            content=Label(text="Images downloaded successfully!"),
            size_hint=(0.5, 0.3)
        )
        popup.open()
    
    def show_error(self, error_msg):
        popup = Popup(
            title="Oops, Error!",
            content=Label(text=f"Download failed: {error_msg}"),
            size_hint=(0.5, 0.3)
        )
        popup.open()

class DownloaderApp(App):
    pass

DownloaderApp().run()

Step 2: Add a Progress Bar to Your KV File (downloader.kv)

We'll add a ProgressBar to visualize download progress:

MainLayout:
<MainLayout>:
    BoxLayout:
        orientation: "vertical"
        padding: "10dp"
        spacing: "10dp"
        
        TextInput:
            id: url_input
            hint_text: "Enter URL"
            write_tab: False
            multiline: False
            on_text_validate: root.on_download()
        
        Button:
            id: download_button
            text: "Download"
            width: "150dp"
            size_hint: None, 1
            on_press: root.on_download()
        
        ProgressBar:
            id: progress_bar
            size_hint: 1, 0.1
            value: 0

Step 3: Modify the Downloader to Support Progress Callbacks (downloader.py)

Update your Downloader class to accept a progress callback and update it during the download process:

from pyppeteer import launch
import aiohttp
import os

class Downloader:
    def __init__(self):
        # Create downloads directory if it doesn't exist
        os.makedirs("./downloads", exist_ok=True)

    async def download_images(self, url, progress_callback):
        browser = await launch(headless=True)
        page = await browser.newPage()
        await page.goto(url, {'waitUntil': 'domcontentloaded'})
        
        # Get all image URLs from the page (replace with your actual script)
        script = self.get_script()
        image_urls = await page.evaluate(script)
        
        total_images = len(image_urls)
        if total_images == 0:
            progress_callback(100)
            await browser.close()
            return
        
        # Download each image asynchronously and update progress
        async with aiohttp.ClientSession() as session:
            for index, img_url in enumerate(image_urls):
                try:
                    await self._download_single_image(session, img_url)
                except Exception as e:
                    print(f"Failed to download {img_url}: {e}")
                
                # Calculate and send progress update
                progress = ((index + 1) / total_images) * 100
                progress_callback(progress)
        
        await browser.close()
    
    async def _download_single_image(self, session, img_url):
        # Save image to downloads directory
        async with session.get(img_url) as response:
            if response.status == 200:
                filename = img_url.split("/")[-1].split("?")[0]  # Clean filename
                with open(f"./downloads/{filename}", "wb") as f:
                    f.write(await response.read())
    
    def get_script(self):
        # Script to extract all image URLs from the page
        return """
            return Array.from(document.querySelectorAll('img')).map(img => img.src);
        """
    
    def convert_images(self, images):
        # TODO: Implement your image conversion logic here
        pass

Key Explanations

  • Background Threads: We use threading.Thread to offload the entire download task (including pyppeteer's async operations) to a background thread. This keeps Kivy's main UI thread free to handle interactions like window movement.
  • Asyncio in Sub-Threads: Pyppeteer requires an asyncio event loop, so we create a new loop in the background thread to avoid conflicts with Kivy's main event loop.
  • Safe UI Updates: Kivy doesn't allow modifying UI widgets from non-main threads. We use Clock.schedule_once to queue progress updates and popups to run on the main thread.
  • Progress Tracking: The progress_callback function lets the downloader send progress updates back to the UI, which we use to update the ProgressBar.

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 09:48:28