从其他应用程序调用时,tkinter 进度条不会更新
tkinter progress bar won't update when called from an other app
我正在使用 tkinter 开发 GUI 来管理数据库中的图像(导入文件、加载文件、查询...)
当扫描新目录及其子目录以查找要放入数据库的新图像时,将启动专用 GUI:
它由一个文本小部件组成,其中打印了当前分析的目录的名称,以及一个显示扫描进度的进度条。
当我单独调用此 GUI 时,只要在每次更改进度条后使用 update(),进度条就会正确更新和前进。另一方面,
即使我不使用更新,文本小部件也会正确更新。
但是,当我从主 GUI 调用它时,进度条没有更新,而文本小部件正确更新。
希望有人能帮忙!
下面是进度条 GUI 的代码。我正在使用 Python 3.6.
from tkinter.filedialog import *
from tkinter.ttk import *
class ScanDirectoryForJPG(Tk):
"""
Inherited from the Tk class
"""
def __init__(self, parent, Path=None):
Tk.__init__(self, parent)
self.parent = parent
self.PathDicom = Path
if self.Path == None:
self.Path = askdirectory(title='Select a directory to scan')
self.title('Scan {} for JPG files'.format(self.Path))
self.status_string = 'Scanning the content of {} folder\n'.format(self.Path)
self.initialize_gui()
self.scan_directory()
def initialize_gui(self):
# Style
self.style = Style()
self.style.theme_use('vista')
# Main window
self.grid()
self.grid_columnconfigure([0], weight=1)
self.grid_rowconfigure([0], weight=1)
# Status
self.status_label = Text(self)
self.status_label.grid(row=0, column=0, sticky='NSEW')
self.status_label.insert(END, 'Looking for JPG files in {}\n'.format(self.Path))
# Progress Bar
self.p = DoubleVar()
self.progress_bar = Progressbar(self, orient='horizontal', mode='determinate', variable=self.p, maximum=100)
self.p.set(0)
self.progress_bar.grid(row=1, column=0, rowspan=1, sticky='EW')
def scan_directory(self):
"""
"""
number_of_files = sum([len(files) for r, d, files in os.walk(self.Path)])
count = 0
for dirName, subdirList, fileList in os.walk(self.Path):
self.status_label.insert(END, '\t-exploring: {}\n'.format(dirName))
self.update()
for filename in fileList:
count += 1
value = count / number_of_files * self.progress_bar['maximum']
if value >= (self.progress_bar['value'] + 1):
# update the progress bar only when its value is increased by at least 1 (avoid too much updates of the progressbar)
self.p.set(self.progress_bar['value'] + 1)
self.update()
file = os.path.join(dirName, filename)
# if the file is a JPG, load it into the database
# ...
# ...
# ..
self.status_label.insert(END, 'FINISH\n')
self.update()
if __name__ == '__main__':
app = ScanDirectoryForJPG(None, Path='D:\Data\Test')
app.mainloop()
print('App closed')
如果你必须在 tkinter 中调用 update()
你就错了。
在这种情况下,您有一个遍历目录树的循环。在整个循环中,您的代码都在该循环中,您没有及时处理事件,因此您必须一直调用 update()
。
相反,捕获 os.walk
的输出或简单地收集顶层目录内容,然后使用 after
一次处理一个项目,传递迭代器或列表,所以一旦你处理单个项目,您再次调用后处理下一个项目。这样,主循环将迅速处理 UI 事件,您的目录树处理将与其他所有事件一起作为事件排队。以这种方式重新设计应用程序后,您应该使应用程序响应更灵敏。
例子
为了证明这一点,os.walk
returns 一个生成器,因此我们可以使用事件后安排每个目录,因为每次调用 next(generator)
都会产生下一个目录及其文件。
为了监控进度,我们需要一些方法来计算要访问的目录或文件的数量,如果此演示用于整个文件系统,应用程序将在其中出现冻结。这也可以分解成 event-based 代码来防止这种影响。
我使用 after(10, ...)
来显示效果,但为了获得最大速度,请改用 after_idle
。
import sys
import os
import tkinter as tk
import tkinter.ttk as ttk
from tkinter.filedialog import askdirectory
class App(ttk.Frame):
def __init__(self, parent, title):
#tk.Frame.__init__(self, parent)
super(App, self).__init__(parent)
parent.wm_withdraw()
parent.wm_title(title)
self.create_ui()
self.grid(sticky = "news")
parent.wm_protocol("WM_DELETE_WINDOW", self.on_destroy)
parent.grid_rowconfigure(0, weight=1)
parent.grid_columnconfigure(0, weight=1)
parent.wm_deiconify()
def create_ui(self):
textframe = ttk.Frame(self)
self.text = text = tk.Text(textframe)
vs = ttk.Scrollbar(textframe, orient=tk.VERTICAL, command=text.yview)
text.configure(yscrollcommand=vs.set)
text.grid(row=0, column=0, sticky=tk.NSEW)
vs.grid(row=0, column=1, sticky=tk.NS)
textframe.grid_columnconfigure(0, weight=1)
textframe.grid_rowconfigure(0, weight=1)
textframe.grid(row=0, column=0, columnspan=2, sticky=tk.NSEW)
self.progressvar = tk.IntVar()
self.progress = ttk.Progressbar(self, variable=self.progressvar)
test_button = ttk.Button(self, text="Walk", command=self.on_walk)
exit_button = ttk.Button(self, text="Exit", command=self.on_destroy)
self.progress.grid(row=1, column=0, sticky=tk.NSEW)
test_button.grid(row=1, column=0, sticky=tk.SE)
exit_button.grid(row=1, column=1, sticky=tk.SE)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
def on_destroy(self):
self.master.destroy()
def on_walk(self):
root = askdirectory()
self.walk(root)
def walk(self, root=None):
if root:
# this is potentially costly, but how to find the number of files to be examined?
count = sum([len(files) for (root,dirs,files) in os.walk(root)])
self.text.delete("1.0", "end")
self.progress.configure(maximum=count)
self.progressvar.set(0)
walker = os.walk(root)
self.after(100, self.do_one, walker)
def do_one(self, walker):
try:
root,dirs,files = next(walker)
for file in files:
self.text.insert(tk.END, os.path.join(root, file), "PATH", "\n", "")
self.text.see(tk.END)
self.progressvar.set(self.progressvar.get() + 1)
self.after(10, self.do_one, walker)
except StopIteration:
pass
def main(args):
root = tk.Tk()
app = App(root, "Walk directory tree")
root.mainloop()
if __name__ == '__main__':
sys.exit(main(sys.argv))
我正在使用 tkinter 开发 GUI 来管理数据库中的图像(导入文件、加载文件、查询...)
当扫描新目录及其子目录以查找要放入数据库的新图像时,将启动专用 GUI: 它由一个文本小部件组成,其中打印了当前分析的目录的名称,以及一个显示扫描进度的进度条。 当我单独调用此 GUI 时,只要在每次更改进度条后使用 update(),进度条就会正确更新和前进。另一方面, 即使我不使用更新,文本小部件也会正确更新。
但是,当我从主 GUI 调用它时,进度条没有更新,而文本小部件正确更新。
希望有人能帮忙!
下面是进度条 GUI 的代码。我正在使用 Python 3.6.
from tkinter.filedialog import *
from tkinter.ttk import *
class ScanDirectoryForJPG(Tk):
"""
Inherited from the Tk class
"""
def __init__(self, parent, Path=None):
Tk.__init__(self, parent)
self.parent = parent
self.PathDicom = Path
if self.Path == None:
self.Path = askdirectory(title='Select a directory to scan')
self.title('Scan {} for JPG files'.format(self.Path))
self.status_string = 'Scanning the content of {} folder\n'.format(self.Path)
self.initialize_gui()
self.scan_directory()
def initialize_gui(self):
# Style
self.style = Style()
self.style.theme_use('vista')
# Main window
self.grid()
self.grid_columnconfigure([0], weight=1)
self.grid_rowconfigure([0], weight=1)
# Status
self.status_label = Text(self)
self.status_label.grid(row=0, column=0, sticky='NSEW')
self.status_label.insert(END, 'Looking for JPG files in {}\n'.format(self.Path))
# Progress Bar
self.p = DoubleVar()
self.progress_bar = Progressbar(self, orient='horizontal', mode='determinate', variable=self.p, maximum=100)
self.p.set(0)
self.progress_bar.grid(row=1, column=0, rowspan=1, sticky='EW')
def scan_directory(self):
"""
"""
number_of_files = sum([len(files) for r, d, files in os.walk(self.Path)])
count = 0
for dirName, subdirList, fileList in os.walk(self.Path):
self.status_label.insert(END, '\t-exploring: {}\n'.format(dirName))
self.update()
for filename in fileList:
count += 1
value = count / number_of_files * self.progress_bar['maximum']
if value >= (self.progress_bar['value'] + 1):
# update the progress bar only when its value is increased by at least 1 (avoid too much updates of the progressbar)
self.p.set(self.progress_bar['value'] + 1)
self.update()
file = os.path.join(dirName, filename)
# if the file is a JPG, load it into the database
# ...
# ...
# ..
self.status_label.insert(END, 'FINISH\n')
self.update()
if __name__ == '__main__':
app = ScanDirectoryForJPG(None, Path='D:\Data\Test')
app.mainloop()
print('App closed')
如果你必须在 tkinter 中调用 update()
你就错了。
在这种情况下,您有一个遍历目录树的循环。在整个循环中,您的代码都在该循环中,您没有及时处理事件,因此您必须一直调用 update()
。
相反,捕获 os.walk
的输出或简单地收集顶层目录内容,然后使用 after
一次处理一个项目,传递迭代器或列表,所以一旦你处理单个项目,您再次调用后处理下一个项目。这样,主循环将迅速处理 UI 事件,您的目录树处理将与其他所有事件一起作为事件排队。以这种方式重新设计应用程序后,您应该使应用程序响应更灵敏。
例子
为了证明这一点,os.walk
returns 一个生成器,因此我们可以使用事件后安排每个目录,因为每次调用 next(generator)
都会产生下一个目录及其文件。
为了监控进度,我们需要一些方法来计算要访问的目录或文件的数量,如果此演示用于整个文件系统,应用程序将在其中出现冻结。这也可以分解成 event-based 代码来防止这种影响。
我使用 after(10, ...)
来显示效果,但为了获得最大速度,请改用 after_idle
。
import sys
import os
import tkinter as tk
import tkinter.ttk as ttk
from tkinter.filedialog import askdirectory
class App(ttk.Frame):
def __init__(self, parent, title):
#tk.Frame.__init__(self, parent)
super(App, self).__init__(parent)
parent.wm_withdraw()
parent.wm_title(title)
self.create_ui()
self.grid(sticky = "news")
parent.wm_protocol("WM_DELETE_WINDOW", self.on_destroy)
parent.grid_rowconfigure(0, weight=1)
parent.grid_columnconfigure(0, weight=1)
parent.wm_deiconify()
def create_ui(self):
textframe = ttk.Frame(self)
self.text = text = tk.Text(textframe)
vs = ttk.Scrollbar(textframe, orient=tk.VERTICAL, command=text.yview)
text.configure(yscrollcommand=vs.set)
text.grid(row=0, column=0, sticky=tk.NSEW)
vs.grid(row=0, column=1, sticky=tk.NS)
textframe.grid_columnconfigure(0, weight=1)
textframe.grid_rowconfigure(0, weight=1)
textframe.grid(row=0, column=0, columnspan=2, sticky=tk.NSEW)
self.progressvar = tk.IntVar()
self.progress = ttk.Progressbar(self, variable=self.progressvar)
test_button = ttk.Button(self, text="Walk", command=self.on_walk)
exit_button = ttk.Button(self, text="Exit", command=self.on_destroy)
self.progress.grid(row=1, column=0, sticky=tk.NSEW)
test_button.grid(row=1, column=0, sticky=tk.SE)
exit_button.grid(row=1, column=1, sticky=tk.SE)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
def on_destroy(self):
self.master.destroy()
def on_walk(self):
root = askdirectory()
self.walk(root)
def walk(self, root=None):
if root:
# this is potentially costly, but how to find the number of files to be examined?
count = sum([len(files) for (root,dirs,files) in os.walk(root)])
self.text.delete("1.0", "end")
self.progress.configure(maximum=count)
self.progressvar.set(0)
walker = os.walk(root)
self.after(100, self.do_one, walker)
def do_one(self, walker):
try:
root,dirs,files = next(walker)
for file in files:
self.text.insert(tk.END, os.path.join(root, file), "PATH", "\n", "")
self.text.see(tk.END)
self.progressvar.set(self.progressvar.get() + 1)
self.after(10, self.do_one, walker)
except StopIteration:
pass
def main(args):
root = tk.Tk()
app = App(root, "Walk directory tree")
root.mainloop()
if __name__ == '__main__':
sys.exit(main(sys.argv))