2

我用 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”(这是名称程序打开和关闭的最后一个文件。

我希望每次程序打开一个新文件时状态栏都会更新文件名。任何帮助表示赞赏!

4

1 回答 1

6

愚蠢的方法是使用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更新了,退出按钮被冻结直到fixFiles完成。这对 GUI 不太友好。以下是一些被认为是有害的update原因。update_idletasks


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

关键是让你的回调函数快速结束。与其使用 long-running for-loop,不如创建一个贯穿 one 内部的函数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()
于 2013-05-22T19:38:31.610 回答