如何在 python 2 tkinter 中同时显示当前任务 运行 的状态并更新进度条而不冻结?

How can I show status of current task running and update the progressbar without freezing at same time in python 2 tkinter?

我的代码显示一个按钮。按下按钮时, 一个文件对话框似乎向用户 select 询问一个文件 (在消息框之后)。这里没问题。

我想更新进度条时出现问题 并显示当前正在执行的任务的状态。

GUI 冻结,进度条和任务状态仅在工作完成后更新。

或者,如果有人可以给我一个 functional/similar 示例来执行此操作,请。

这是我正在处理的实际文件 (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()

我试图找到可以帮助我解决这个问题的例子:

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

还有更多...

但是这些对我来说似乎还很困难,因为我是 python 的新手并且 我不知道如何在没有 freezing/crashing GUI 的情况下放置 tkfiledialog。

我创建了与线程通信的队列

self.queue = Queue.Queue()

和运行 具有获取队列作为参数的函数的线程。

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

线程将运行一些长运行宁代码并使用队列向主线程发送消息。
它将不会显示任何消息框或更改小部件中的值。

我在启动线程之前请求文件 - 所以最终线程不使用任何 tkinter 的小部件或 window。

主线程使用 after() 定期 运行 函数检查队列,如果有消息,它获取消息并更新 window 中的标签。它还会更改 Progressbar 中的值。我用 mode="determinate" 不用 progressbar.start().

如果消息是 "TASKS FINISHED" 则函数不会再次检查队列。


代码可以满足您的需要。

我删除了你在代码中的所有评论,只有我的评论。

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