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

Kivy ProgressBar更新异常求助:值增长但界面未刷新

问题:Kivy ProgressBar控件值更新但界面未同步

我正在开发一款可生成带图表PDF报告的简易应用,计划添加进度窗口展示程序执行进度。目前功能初步实现,但遇到ProgressBar控件异常:执行任务时progressbar的value值已增长,可界面控件未同步更新。

使用环境:Python 3.10、Kivy 2.2.1

导入的库

import os
import re
import time
from fpdf import FPDF
from matplotlib import pyplot as plt
import pandas as pd

# set window size
from kivy.config import Config
Config.set('graphics', 'width', '450')
Config.set('graphics', 'height', '300')

import kivy
from kivy.app import App
from kivy.uix.progressbar import ProgressBar
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.filechooser import FileChooserListView
from kivy.uix.popup import Popup
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.clock import Clock

GeneratePDF类实现

class FileChooserPopup(Popup):
    pass        

class GeneratePDF():
    def __init__(self, app_instance, **kwargs):
        super().__init__(**kwargs)
        self.app_instance = app_instance


    # --- other methods ---

    def create_progress_popup(self):
        # show progress popup
        content = BoxLayout(orientation='vertical')
        self.progress_bar = ProgressBar(max=100, value=0, size_hint_x=0.8,
                                        pos_hint={'center_x':0.5, 'center_y':0.5})
        self.percent_label = Label(text='0%', size_hint=(None, None),
                                size = (40,30),
                                pos_hint ={'center_x': 0.5,'center_y': 0.5})
        content.add_widget(self.progress_bar)
        content.add_widget(self.percent_label)
        self.progress = Popup(title='', content=content, size_hint=(0.8,0.8),
                              separator_height=0)
       
        # dismiss the file chooser popup
        self.app_instance.file_chooser.dismiss()

        self.progress.open()

    def update_progress(self, value):
        current = self.progress_bar.value
        current += value
        self.progress_bar.value = current
        self.percent_label.text = f'{int(current)}%'

    # --- other methods ---

    def generate_raport1(self, instance, selection, *args):
        # opening file
        if selection:
            self.selected_file = selection[0]

            # Create the progress popup
            self.create_progress_popup()

            # start generating raport inthread
            Clock.schedule_once(lambda dt: self.generate_report2(), 0)
        else:
            # dismiss the file chooser popup
            self.app_instance.file_chooser.dismiss()

    def generate_report2(self):
       
        # drawing plots and updating progressbar
        self.plot1(self.selected_file)
        self.update_progress(20)

        self.plot2()
        self.update_progress(20)

        self.plot3()
        self.update_progress(20)

        self.plot4()
        self.update_progress(20)

        # generate pdf raport
        self.write_pdf()
        self.update_progress(20)
        time.sleep(1)

        # deleting unnecessary png files
        for plot in self.plots: os.remove(plot)
       
        # showing message
        self.show_message_box()    

App类实现

class RaportApp(App):
   
    def build(self):
        self.layout = RelativeLayout()
        self.generate_pdf = GeneratePDF(app_instance=self)
        self.button = Button(text="Choose File", on_release=self.show_file_chooser,
                             size_hint=(None,None), size=(200,50),
                             pos_hint={'center_x':0.5,'center_y':0.5})
        self.layout.add_widget(self.button)
        return self.layout
   
    def show_file_chooser(self, *args):
        username = os.getenv("USERNAME")
        content = FileChooserListView(path = f'C:\\Users\\{username}\\Desktop', filters=['*.csv'])
        self.file_chooser = FileChooserPopup(title="Choose a *.csv file", content=content, size_hint=(0.8, 0.8))
        content.bind(on_submit=self.generate_pdf.generate_raport1)
        self.file_chooser.open()

    def update_progress(self, value):
        self.generate_pdf.update_progress(value)

if __name__ == '__main__':
    RaportApp().run()

原因分析

Kivy的UI渲染完全依赖主线程,你的generate_report2方法虽然用Clock.schedule_once调度,但本质还是在主线程中执行。而绘图、PDF生成都是耗时任务,会直接阻塞主线程,导致UI无法及时刷新——ProgressBar的数值更新操作会被积压,直到整个任务执行完毕才会一次性显示。另外,time.sleep(1)也会进一步阻塞主线程,加剧卡顿。

解决方案

1. 用线程隔离耗时任务

把所有绘图、PDF生成的耗时操作放到单独线程中执行,避免阻塞主线程。同时,所有UI更新操作必须通过Clock.schedule_once调度回主线程执行(Kivy禁止在子线程中操作UI控件)。

修改核心代码如下:

import threading  # 新增导入线程库

# 修改generate_raport1方法
def generate_raport1(self, instance, selection, *args):
    if selection:
        self.selected_file = selection[0]
        self.create_progress_popup()
        # 启动子线程执行耗时任务
        threading.Thread(target=self.generate_report2, daemon=True).start()
    else:
        self.app_instance.file_chooser.dismiss()

# 修改generate_report2方法
def generate_report2(self):
    # 绘图操作+调度UI更新
    self.plot1(self.selected_file)
    Clock.schedule_once(lambda dt: self.update_progress(20))

    self.plot2()
    Clock.schedule_once(lambda dt: self.update_progress(20))

    self.plot3()
    Clock.schedule_once(lambda dt: self.update_progress(20))

    self.plot4()
    Clock.schedule_once(lambda dt: self.update_progress(20))

    self.write_pdf()
    Clock.schedule_once(lambda dt: self.update_progress(20))
    
    # 替换线程内的sleep(如果非必须建议直接删除)
    time.sleep(1)

    for plot in self.plots: os.remove(plot)
    
    # 弹窗显示也必须调度回主线程
    Clock.schedule_once(lambda dt: self.show_message_box())

2. 移除不必要的阻塞

如果time.sleep(1)只是为了让用户看到100%的进度,建议用Clock.schedule_once延迟执行后续操作,避免阻塞线程:

# 替换原time.sleep(1)和后续代码
Clock.schedule_once(lambda dt: self.finish_task(), 1)

# 新增finish_task方法
def finish_task(self):
    for plot in self.plots: os.remove(plot)
    self.show_message_box()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 09:04:51