Python 3 and Tkinter: progress bar does not update during script execution

0

Hello. I'm creating a layout converter using Python 3.6.4 and Tkinter.

Among other things, the GUI should have a progress bar that updates its value with each interaction of the conversion process, for example, every processed line of the file being converted, the progress bar should be updated.

The problem I am facing is that the program crashes while you are performing the conversion and the toolbar is not updated. The bar only refreshes at the end of the conversion operation.

The basic part of the script is this (I removed everything that is not relevant and left only the essentials to see where the problem is or where I'm going wrong):

import sys
from time import sleep
from tkinter import *
from tkinter.ttk import Progressbar

class Gui:

    def __init__(self):
        self.Window = Tk()
        self.Window.geometry('{0}x{1}'.format(600, 400))

        self.progress = StringVar()
        self.progress.set(0)
        self.progressBar = Progressbar(self.Window, maximum=100, orient=HORIZONTAL, variable=self.progress)
        self.progressBar.grid(row=0, column=0, sticky=W + E)

        self.startButton = Button(self.Window, text='Iniciar', command=self.start)
        self.startButton.grid(row=0, column=2)

    def start(self):
        for t in range(0,100):
            self.progress.set(t)
            sleep(0.1)

    def run(self):
        self.Window.mainloop()
        return 0

if __name__ == '__main__':
    Gui = Gui()
    sys.exit(Gui.run())

I am a beginner in Python but I have a long experience (fifteen years) with PHP and etc.

    
asked by anonymous 12.03.2018 / 19:01

2 answers

1

The problem you are having is that tkinter, (as well as other graphical toolkits in various languages such as GTK, Qt, etc ...) run on a single thread. A graphical program always has a main loop, which is where the framework has the code to collect all the events that arrive the application, and call the corresponding functions for them to be processed.

From here you configure your application to when the button is pressed, the start method must be called - and passes the control to the mainloop of Tkinter. When the button is pressed, tkinter takes the mouse event from the operating system, creates the internal event, and calls the start function - and this in turn does not return control to the tkinter . Instead it does 100 updates on self.progress , and only then does it return the control. When start has just run, tkinter will process all of its internal events with% progress_%.

Moreover, in every call to set , your program is stopped, without responding to external events or anything - because the code that does this is in the main loop.

And how to fix it? Simple: In no graphics program do you call time.sleep or equivalent function (unless it is integrated with the framework). In the case of tkinter, the correct thing is to make a call to tine.sleep saying a method to be called from there in so many milliseconds. You do this scheduling, and return the control to the main tkinter loop. Of course the status of the progress bar should be stored somewhere between one chaar in another - as you are already inside a class, just use a specific attribute for that.

class Gui:

    def __init__(self):
        ...
        self.progress_count = 0


    def start(self):
        self.progress.set(self.progress_count)
        self.progress_count += 1
        if self.progress_count < 100:
             self.Window.after(100, self.start)
    
12.03.2018 / 20:59
0

After hours of searching last night, I found something that also solved the problem: using self.Window.update_idletasks () inside the loop.

  

Calls all pending idle tasks, without processing any other events. This can be used to carry out geometry management and redraw widgets if necessary, without calling any callbacks.

So, the final code (with some minor adjustments) is thus

class Gui:

    def __init__(self):
        self.Window = Tk()
        self.Window.geometry('{0}x{1}'.format(600, 400))

        self.progress = DoubleVar()
        self.progress.set(0)
        self.progressBar = Progressbar(self.Window, maximum=100, orient=HORIZONTAL, variable=self.progress)
        self.progressBar.grid(row=0, column=0, sticky=W + E)

        self.startButton = Button(self.Window, text='Iniciar', command=self.start)
        self.startButton.grid(row=0, column=2)

    def start(self):
        for t in range(0, 100, 20):
            self.progress.set(t)
            print(t)
            self.Window.update_idletasks()
            sleep(2)
        self.progress.set(100)
        print('Fim...')

    def run(self):
        self.Window.mainloop()
        return 0

if __name__ == '__main__':
    Gui = Gui()
    sys.exit(Gui.run())
    
13.03.2018 / 12:14