Skip to main content
 首页 » 编程设计

Python/Tkinter 状态栏未正确更新

2025年05月04日19lonelyxmas

我使用 Tkinter 在 Python (2.7) 中开发了一个简单的应用程序。但我的状态栏只是有点工作。这是精简代码:

from Tkinter import * 
import os 
import sys 
 
def fixFiles(): 
    inputFilePath= input_dir.get() 
 
    #Build a list of files in a directory 
    fileList = os.listdir(inputFilePath) 
 
    #Loop through those files, open the file, do something, close the file 
    for filename in fileList: 
        infile = open(inputfilepath + "/" + filename,'r') 
 
        #Update the status with the filename 
        status_string = 'Status: Working on file: ' + str(filename) 
        status.set(status_string) 
 
        for line in infile: 
            #Do some stuff here 
        infile.close()   
 
class App: 
    def __init__(self, master): 
            i = 0 
        status.set("Status: Press 'Fix Files!'") 
        statuslabel = Label(master, textvariable=status, relief = RIDGE, width = 65, pady = 5, anchor=W) 
        bFixFiles = Button(root, text='Fix Files!', command = fixFiles) 
        bQuit = Button(root, text='Quit', command = root.destroy) 
 
        statuslabel.grid(row=i, column = 0, columnspan = 2) 
        bFixFiles.grid(row=i, column=2, sticky=E) 
        bQuit.grid(row=i, column=3, sticky=W) 
 
root = Tk() 
root.title("FIX Files") 
input_dir = StringVar() 
status = StringVar() 
choice = IntVar() 
app = App(root) 
root.mainloop() 

当前发生的情况是状态栏显示“状态:按‘修复文件!’”,直到程序完成文件循环,此时它显示“状态:正在处理文件:XXXXX.txt”(是程序打开和关闭的最后一个文件的名称。

每次程序打开一个新文件时,我希望状态栏更新为文件名。感谢您的帮助!

请您参考如下方法:

愚蠢的方法是使用 root.update_idletasks():

#Update the status with the filename 
status_string = 'Status: Working on file: ' + str(filename) 
status.set(status_string) 
root.update_idletasks() 

值得赞扬的是,它很简单,但它并没有真正起作用——虽然 statuslabel 得到了更新,但 Quit 按钮被卡住,直到 fixFiles 完成。这对 GUI 不是很友好。 Here are some more reasons为什么 update 和 update_idletasks 被认为是有害的。


那么我们应该如何在不卡住 GUI 的情况下运行长时间运行的任务?

关键是让你的回调函数快速结束。与其使用长时间运行的 for-loop,不如创建一个在 for-loop 内部运行一次的函数。希望结束得足够快,让用户不会觉得 GUI 被卡住了。

然后,要替换 for-loop,您可以使用对 root.after 的调用来多次调用您的快速运行函数。


from Tkinter import * 
import tkFileDialog 
import os 
import sys 
import time 
 
 
def startFixFiles(): 
    inputFilePath = tkFileDialog.askdirectory() 
    # inputFilePath= input_dir.get() 
 
    # Build a list of files in a directory 
    fileList = os.listdir(inputFilePath) 
    def fixFiles(): 
        try: 
            filename = fileList.pop() 
        except IndexError: 
            return 
        try: 
            with open(os.path.join(inputFilePath, filename), 'r') as infile: 
                # Update the status with the filename 
                status_string = 'Status: Working on file: ' + str(filename) 
                status.set(status_string) 
                for line in infile: 
                    # Do some stuff here 
                    pass 
        except IOError: 
            # You might get here if file is unreadable, you don't have read permission, 
            # or the file might be a directory... 
            pass 
        root.after(250, fixFiles) 
    root.after(10, fixFiles) 
 
class App: 
    def __init__(self, master): 
        i = 0 
        status.set("Status: Press 'Fix Files!'") 
        statuslabel = Label( 
            master, textvariable=status, relief=RIDGE, width=65, 
            pady=5, anchor=W) 
        bFixFiles = Button(root, text='Fix Files!', command=startFixFiles) 
        bQuit = Button(root, text='Quit', command=root.destroy) 
 
        statuslabel.grid(row=i, column=0, columnspan=2) 
        bFixFiles.grid(row=i, column=2, sticky=E) 
        bQuit.grid(row=i, column=3, sticky=W) 
 
root = Tk() 
root.title("FIX Files") 
input_dir = StringVar() 
status = StringVar() 
choice = IntVar() 
app = App(root) 
root.mainloop() 

上面引出了一个问题,如果我们的长时间运行的任务没有循环,我们该怎么办?或者即使一次通过循环也需要很长时间?

这里有一种方法可以在单独的进程(或线程)中运行长时间运行的任务,并让它通过队列传递信息,主进程可以定期轮询(使用 root.after ) 更新 GUI 状态栏。我认为这种设计通常更容易适用于这个问题,因为它不需要您拆分 for-loop。

请注意,所有与 Tkinter GUI 相关的函数调用必须发生在单个线程中。这就是为什么长时间运行的进程只是通过队列发送字符串,而不是尝试直接调用 status.set。

import Tkinter as tk 
import multiprocessing as mp 
import tkFileDialog 
import os 
import Queue 
 
sentinel = None 
 
def long_running_worker(inputFilePath, outqueue): 
    # Build a list of files in a directory 
    fileList = os.listdir(inputFilePath)   
    for filename in fileList: 
        try: 
            with open(os.path.join(inputFilePath, filename), 'r') as infile: 
                # Update the status with the filename 
                status_string = 'Status: Working on file: ' + str(filename) 
                outqueue.put(status_string) 
                for line in infile: 
                    # Do some stuff here 
                    pass 
        except IOError: 
            # You might get here if file is unreadable, you don't have read permission, 
            # or the file might be a directory... 
            pass 
    # Put the sentinel in the queue to tell update_status to end 
    outqueue.put(sentinel) 
 
class App(object): 
    def __init__(self, master): 
        self.status = tk.StringVar() 
        self.status.set("Status: Press 'Fix Files!'") 
        self.statuslabel = tk.Label( 
            master, textvariable=self.status, relief=tk.RIDGE, width=65, 
            pady=5, anchor='w') 
        bFixFiles = tk.Button(root, text='Fix Files!', command=self.startFixFiles) 
        bQuit = tk.Button(root, text='Quit', command=root.destroy) 
        self.statuslabel.grid(row=1, column=0, columnspan=2) 
        bFixFiles.grid(row=0, column=0, sticky='e') 
        bQuit.grid(row=0, column=1, sticky='e') 
 
    def update_status(self, outqueue): 
        try: 
            status_string = outqueue.get_nowait() 
            if status_string is not sentinel: 
                self.status.set(status_string) 
                root.after(250, self.update_status, outqueue) 
            else: 
                # By not calling root.after here, we allow update_status to truly end 
                pass 
        except Queue.Empty: 
            root.after(250, self.update_status, outqueue) 
 
    def startFixFiles(self): 
        inputFilePath = tkFileDialog.askdirectory() 
        # Start long running process 
        outqueue = mp.Queue()     
        proc = mp.Process(target=long_running_worker, args=(inputFilePath, outqueue)) 
        proc.daemon = True 
        proc.start() 
        # Start a function to check a queue for GUI-related updates 
        root.after(250, self.update_status, outqueue) 
 
root = tk.Tk() 
root.title("FIX Files") 
app = App(root) 
root.mainloop()