¿Cómo puedo mostrar el estado de la tarea actual en ejecución y actualizar la barra de progreso sin congelar al mismo tiempo en python 2 tkinter?

Mi código muestra un botón. Cuando se presiona el botón, aparece un archivo de diálogo para solicitar al usuario que seleccione un archivo (después de un mensaje). No hay problema aquí.

Mi problema se produce cuando quiero actualizar la barra de progreso y mostrar el estado de la tarea actual en ejecución.

La GUI se congela, y la barra de progreso y el estado de la tarea se actualizan solo después de que se terminó el trabajo.

O, si alguien me puede dar un ejemplo funcional / similar para hacer esto, por favor.

Este es el archivo real en el que estoy trabajando (Python 2):

# -*- coding: utf-8 -*- import os import Tkinter import ttk import tkMessageBox import tkFileDialog import base64 import threading import Queue import subprocess import sys import time #here write my tasks class Tareas(): def __init__(self, parent, row, column, columnspan): self.parent = parent self.length=200 self.value=0 self.maximum=100 self.interval=10 #I changed this from the original code - progressbar self.barra_progreso = ttk.Progressbar(parent, orient=Tkinter.HORIZONTAL, length = self.length, mode="determinate", value=self.value, maximum=self.maximum) self.barra_progreso.grid(row=row, column=column, columnspan=columnspan) #creating a thread to avoid gui freezing self.thread = threading.Thread() # status label tite (this does not change) self.lbl_estado = Tkinter.Label(parent, text='STATUS:') self.lbl_estado.grid(row=9, column=0, padx = 20, pady = 5) # creating the status variable and declaring its value self.estado_aplicacion = Tkinter.StringVar() self.estado_aplicacion.set("Started, waiting for a task...") # ***HERE I WANT DISPLAY CURRENT TASK RUNNING*** self.lbl_info_estado = Tkinter.Label(parent, text=self.estado_aplicacion.get(), textvariable=self.estado_aplicacion) self.lbl_info_estado.grid(row=10, column=0, padx = 20, pady = 5) def extraerDatosArchivo(self): #task 1 print 'tarea 1' #CHANGING TASK STATUS self.estado_aplicacion.set('Seleccionando respaldo válido... (1/6)') #displaying a messagebox to indicate to user choose a backup tkMessageBox.showinfo('INFORMACIÓN', 'Select file to decrypt.') #asking for a backup archivo_respaldo = tkFileDialog.askopenfile(initialdir="/", title="Select file", filetypes=(("All files", "*.*"), ("All files2", "*.*")) ) #getting file print 'archivo a desencriptar: ', archivo_respaldo #checking if a file exists if archivo_respaldo is None or not archivo_respaldo: tkMessageBox.showerror('ERROR', 'No seleccionó nada.') return None #stop task without close gui ###activating progressbar if not self.thread.isAlive(): VALUE = self.barra_progreso["value"] self.barra_progreso.configure(mode="indeterminate", maximum=self.maximum, value=VALUE) self.barra_progreso.start(self.interval) ### #CHANGING TASK STATUS self.estado_aplicacion.set('Copiando clave privada... (2/6)') #simulating long task time.sleep(4) print '2' #CHANGING TASK STATUS self.estado_aplicacion.set('Creando carpeta de trabajo... (3/6)') #simulating long task time.sleep(4) print '3' #CHANGING TASK STATUS self.estado_aplicacion.set('TASKS FINISHED') #displaying task finished succesfully tkMessageBox.showinfo('INFORMATION', 'Done!.') #gui tool, buttons, bla, bla, and more... class GUI(Tkinter.Frame): """ class to define tkinter GUI""" def __init__(self, parent,): Tkinter.Frame.__init__(self, master=parent) """desde aca se va controlar la progressbar""" tareas = Tareas(parent, row=8, column=0, columnspan=2) #putting prog bar #button for task 1 btn_extraer_datos_archivo = Tkinter.Button(parent, text = 'Select file', width=24, height=2, command=tareas.extraerDatosArchivo, state='normal') btn_extraer_datos_archivo.grid(row=2, column=0, padx = 40, pady = 5) root = Tkinter.Tk() root.title('Extractor de datos 1.0')#title tool root.minsize(200, 200)#bla bla... root.resizable(0,0)#disabling resizing herramienta = GUI(root) root.mainloop() 

Intenté encontrar ejemplos que pudieran ayudarme en esto:

¿Cómo conectar una barra de progreso a una función?

https://reformatcode.com/code/python/tkinter-how-to-use-threads-to-preventing-main-event-loop-from-quotfreezingquot

http://pythonexample.com/snippet/python/progresspy_rtogo_python

http://pythonexample.com/snippet/python/progresspy_c02t3x_python

https://www.reich13.tech/python-how-to-get-progressbar-start-info-from-one-window-class-to-other-5a26adfbcb90451297178f35

https://www.python-forum.de/viewtopic.php?f=18&t=19150

y más…

Pero esto aún me parece difícil porque soy un novato en python y no tengo idea de cómo poner tkfiledialog en aquellos sin congelar / estrellar la GUI.

Creo la cola para la comunicación con el hilo.

 self.queue = Queue.Queue() 

y ejecute el hilo con la función que obtiene la cola como parámetro.

 self.thread = threading.Thread(target=self.my_function, args=(self.queue,)) 

El subproceso ejecutará algunos códigos de larga duración y usará la cola para enviar mensajes al subproceso principal.
NO mostrará ningún cuadro de mensaje ni cambiará los valores en los widgets.

Pido un archivo antes de iniciar el hilo, por lo que finalmente el hilo no usa ningún widget o ventana de tkinter.

El hilo principal utiliza after() para ejecutar periódicamente la función que verifica la cola y si hay un mensaje, recibe un mensaje y actualiza la etiqueta en la ventana También cambia el valor en la Progressbar de Progressbar . Uso mode="determinate" y no uso progressbar.start() .

Si el mensaje es "TASKS FINISHED" , la función no vuelve a revisar la cola.


El código funciona como probablemente necesites.

He eliminado todos sus comentarios en el código y solo hay mis comentarios.

 import os import Tkinter import ttk import tkMessageBox import tkFileDialog import threading import Queue #import sys import time class Tareas(): def __init__(self, parent, row, column, columnspan): self.parent = parent self.length=200 self.value=0 self.maximum=100 self.interval=10 self.barra_progreso = ttk.Progressbar(parent, orient=Tkinter.HORIZONTAL, length = self.length, mode="determinate", value=self.value, maximum=self.maximum) self.barra_progreso.grid(row=row, column=column, columnspan=columnspan) self.lbl_estado = Tkinter.Label(parent, text='STATUS:') self.lbl_estado.grid(row=9, column=0, padx = 20, pady = 5) self.estado_aplicacion = Tkinter.StringVar() self.estado_aplicacion.set("Started, waiting for a task...") self.lbl_info_estado = Tkinter.Label(parent, text=self.estado_aplicacion.get(), textvariable=self.estado_aplicacion) self.lbl_info_estado.grid(row=10, column=0, padx = 20, pady = 5) def extraerDatosArchivo(self): print 'tarea 1' # do some job before you run thread self.estado_aplicacion.set('Seleccionando respaldo válido... (1/6)') tkMessageBox.showinfo('INFORMACIÓN', 'Select file to decrypt.') archivo_respaldo = tkFileDialog.askopenfile(initialdir="/home/furas", title="Select file", filetypes=(("All files", "*.*"), ("All files2", "*.*")) ) print 'archivo a desencriptar: ', archivo_respaldo if archivo_respaldo is None or not archivo_respaldo: tkMessageBox.showerror('ERROR', 'No seleccionó nada.') return # --- (re)set progressbar --- # set progressbar for 6+1 steps and `mode="determinate"`. # because first step is already done so set value=1 self.barra_progreso.configure(#mode="indeterminate", maximum=7, value=1) # don't start progresbar - I will change it manually #self.barra_progreso.start()#self.interval) # --- here starts thread --- # create queue for communication with thread self.queue = Queue.Queue() # create thread and send queue as argument self.thread = threading.Thread(target=self.my_function, args=(self.queue,)) # start thread self.thread.start() # start checking queue self.check_queue() def check_queue(self): print("check queue") # check if something in queue # because `queue.get()` may block program when it waits for message if not self.queue.empty(): # get message from queue text = self.queue.get() print("get text from queue:", text) # change status self.estado_aplicacion.set(text) # TODO: you can update progressbar self.barra_progreso['value'] += 1 # check if it is last message if text == 'TASKS FINISHED': # stop progersbar self.barra_progreso.stop() #displaying task finished succesfully tkMessageBox.showinfo('INFORMATION', 'Done!.') # exit without running `root.after()` again return # check queue after 200ms (0.2s) so mainloop will can do its job root.after(200, self.check_queue) def my_function(self, queue): #CHANGING TASK STATUS queue.put('Copiando clave privada... (2/6)') #simulating long task time.sleep(4) print '2' #CHANGING TASK STATUS queue.put('Creando carpeta de trabajo... (3/6)') #simulating long task time.sleep(4) print '3' #CHANGING TASK STATUS queue.put('Creando carpeta de trabajo... (4/6)') #simulating long task time.sleep(4) print '4' #CHANGING TASK STATUS queue.put('Creando carpeta de trabajo... (5/6)') #simulating long task time.sleep(4) print '5' #CHANGING TASK STATUS queue.put('Creando carpeta de trabajo... (6/6)') #simulating long task time.sleep(4) print '6' #CHANGING TASK STATUS queue.put('TASKS FINISHED') class GUI(Tkinter.Frame): """ class to define tkinter GUI""" def __init__(self, parent,): Tkinter.Frame.__init__(self, master=parent) tareas = Tareas(parent, row=8, column=0, columnspan=2) btn_extraer_datos_archivo = Tkinter.Button(parent, text = 'Select file', width=24, height=2, command=tareas.extraerDatosArchivo, state='normal') btn_extraer_datos_archivo.grid(row=2, column=0, padx = 40, pady = 5) # --- main --- root = Tkinter.Tk() root.title('Extractor de datos 1.0') root.minsize(200, 200) root.resizable(0,0) herramienta = GUI(root) root.mainloop()