How can I show status of current task running and

2020-05-03 10:28发布

问题:

My code displays a button. When button is pressed, a filedialog appears to ask to user select a file (after a messagebox). No problem here.

My problem occurs when I want update the progressbar and show the status of current task in execution.

The GUI freezes, and the progressbar and task status are updated only after work was finished.

Or, if anyone can give me a functional/similar example to do this, please.

This is the actual file I'm working on (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()

I tried to find examples that could helped me in this:

How to connect a progress bar to a function?

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

and more...

But these seems difficult yet for me because I'm newbie in python and I don´t have idea how to put tkfiledialog in those without freezing/crashing the GUI.

回答1:

I create queue for communication with thread

self.queue = Queue.Queue()

and run thread with function which gets queue as parameter.

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

Thread will run some long-running code and use queue to send messages to main thread.
It will NOT display any message box or change values in widgets.

I ask for file before starting thread - so finally thread doesn't use any tkinter's widget or window.

Main thread uses after() to periodically run function which checks queue and if there is message it gets message and updates Label in window. It also changes value in Progressbar. I use mode="determinate" and don't use progressbar.start().

If message is "TASKS FINISHED" then function doesn't check queue again.


Code works as you probably need.

I removed all your comments in code and there are only my comments.

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