La barra de progreso de tkinter no se actualizará cuando se llame desde otra aplicación

Estoy desarrollando una GUI con tkinter para administrar imágenes en una base de datos (importar archivo, cargar archivo, consultar, …)

Cuando un nuevo directorio y sus subdirectorios se escanean en busca de nuevas imágenes para colocarlas en la base de datos, se inicia una GUI dedicada: consiste en un widget de texto donde se imprime el nombre del directorio actualmente analizado, y una barra de progreso que muestra el Progreso de la exploración. Cuando solo llamo a esta GUI, la barra de progreso se actualiza y progresa correctamente siempre que use update () después de cada cambio en la barra de progreso. Por otro lado, el widget de texto se está actualizando correctamente incluso si no uso la actualización.

Sin embargo, la barra de progreso no se actualiza como debería cuando lo llamo desde la GUI principal, mientras que el widget de texto se actualiza correctamente.

¡Espero que alguien pueda ayudar!

A continuación se muestra el código para la GUI de la barra de progreso. Estoy usando 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') 

Si tiene que llamar a update() en tkinter, lo está haciendo mal.

En este caso tienes un bucle que recorre el árbol de directorios. Durante todo el tiempo que su código está dentro de ese bucle, no está procesando los eventos rápidamente, por lo que debe llamar a update() todo el tiempo.

En su lugar, capture la salida de os.walk o simplemente recopile los contenidos del directorio de nivel os.walk y luego use after para procesar un solo elemento a la vez, pasando el iterador o la lista para que una vez que procese un solo elemento, vuelva a llamar después para procesar el siguiente. De esta manera, mainloop manejará los eventos de la interfaz de usuario rápidamente y el procesamiento del árbol de directorios se pondrá en cola como eventos junto con todo lo demás. Debería multar la aplicación con mayor capacidad de respuesta una vez que la modifique de esta manera

Ejemplo

Para demostrar esto, os.walk devuelve un generador para que podamos usar después de los eventos para progtwigr cada directorio, ya que cada llamada de next(generator) genera el siguiente directorio con sus archivos.

Para monitorear el progreso, necesitamos alguna forma de contar la cantidad de directorios o archivos que se visitarán y si esta demostración se usa para un sistema de archivos completo que es donde la aplicación parece congelarse. Esto también podría dividirse en código basado en eventos para evitar este efecto.

Utilicé after(10, ...) para que muestre el efecto, pero para la velocidad máxima, use after_idle en after_idle lugar.

 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))