问题描述
限时送ChatGPT账号..我用 Tkinter 用 Python (2.7) 开发了一个简单的应用程序.但是我的状态栏只能正常工作.这是精简后的代码:
I have developed a simple app in Python (2.7) with Tkinter. But my status bar is only sort of working. Here's the stripped down code:
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"(其中是程序最后打开和关闭的文件名.
Currently what's happening is that the status bar reads "Status: Press 'Fix Files!'" until the program is finished looping through the files, at which point it reads "Status: Working on file: XXXXX.txt" (which is the name of the last file to be opened and closed by the program.
我希望每次程序打开一个新文件时状态栏都会更新文件名.任何帮助表示赞赏!
I would like the status bar to update with the file name each time the program opens a new file. Any help is appreciated!
推荐答案
愚蠢的方法是使用 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
被认为是有害的.
To its credit, it is simple, but it does not really work -- although the statuslabel
gets updated, the Quit button is frozen until fixFiles
is completed. That's not very GUI-friendly. Here are some more reasons why update
and update_idletasks
are considered harmful.
那么我们应该如何在不冻结 GUI 的情况下运行长时间运行的任务?
So how should we run a long-running task without freezing the GUI?
关键是让你的回调函数快速结束.与其使用长时间运行的 for 循环
,不如创建一个运行一次 for 循环
内部的函数.希望这结束得足够快,让用户不会觉得 GUI 已被冻结.
The key is to make your callback functions end quickly. Instead of having a long-running for-loop
, make a function that runs through the innards of the for-loop
once. Hopefully that ends quickly enough for the user to not feel the GUI has been frozen.
然后,要替换for 循环
,您可以使用对root.after
的调用多次调用您的快速运行函数.
Then, to replace the for-loop
, you could use calls to root.after
to call your quick-running function multiple times.
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()
<小时>
上面引出了一个问题,如果我们的长时间运行的任务没有循环,我们该怎么办?或者即使一次通过循环也需要很长时间?
The above begs the question, What should we do if our long-running task has no loop? or if even one pass through the loop requires a long time?
这是一种在单独的进程(或线程)中运行长时间运行的任务的方法,并让它通过主进程可以定期轮询的队列(使用 root.after
) 来更新 GUI 状态栏.我认为这种设计一般更容易适用于这个问题,因为它不需要你分解 for 循环
.
Here is a way to run the long-running task in a separate process (or thread), and have it communicate information through a queue which the main process can periodically poll (using root.after
) to update the GUI status bar. I think this design is more easily applicable to this problem in general since it does not require you to break apart the for-loop
.
请注意,所有与 Tkinter GUI 相关的函数调用必须发生在单个线程中.这就是为什么长时间运行的进程只是通过队列发送字符串而不是尝试直接调用 status.set
.
Note carefully that all Tkinter GUI-related function calls must occur from a single thread. That is why the long-running process simply sends strings through the queue instead of trying to call status.set
directly.
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()
这篇关于Python/Tkinter 状态栏未正确更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
更多推荐
[db:关键词]
发布评论