[英]Python / Tkinter status bar not updating correctly
我使用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
是,它很简单,但实际上并不起作用-尽管statuslabel
得到更新,但Quit按钮被冻结,直到fixFiles
完成。 那不是非常友好的GUI。 这是为什么将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()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.