20

I have a simple Tk GUI and a long process in a function attached to a button. I want a progress bar when I click on the button, just like it starts a long process.

How can I do that? This is my current code:

from tkinter import Button, Tk, HORIZONTAL

from tkinter.ttk import Progressbar
import time


class MonApp(Tk):
    def __init__(self):
        super().__init__()

        bt1 = Button(self, text='Traitement', command=self.traitement)
        bt1.grid()
        self.progress = Progressbar(self, orient=HORIZONTAL, length=100, mode='indeterminate')
        self.progress.grid()
        self.progress.grid_forget()

    def traitement(self):
        self.progress.grid()
        self.progress.start()
        time.sleep(15) 
        ## Just like you have many, many code lines...

        self.progress.stop()


if __name__ == '__main__':
    app = MonApp()
    app.mainloop()

How can I put a progress bar in that application?

wovano
  • 3,264
  • 4
  • 18
  • 43
j666
  • 319
  • 1
  • 2
  • 8

2 Answers2

30

You can find ttk.Progressbar at tkdocs

import time
from tkinter import *
from tkinter.ttk import *

tk = Tk()
progress = Progressbar(tk, orient=HORIZONTAL, length=100, mode='determinate')


def bar():
    progress['value'] = 20
    tk.update_idletasks()
    time.sleep(1)
    progress['value'] = 50
    tk.update_idletasks()
    time.sleep(1)
    progress['value'] = 80
    tk.update_idletasks()
    time.sleep(1)
    progress['value'] = 100

progress.pack()
Button(tk, text='foo', command=bar).pack()
mainloop()

It's better to use threading and run your code in another thread.

Like this:

import threading
import time
from tkinter import Button, Tk, HORIZONTAL
from tkinter.ttk import Progressbar

class MonApp(Tk):
    def __init__(self):
        super().__init__()

        self.btn = Button(self, text='Traitement', command=self.traitement)
        self.btn.grid(row=0, column=0)
        self.progress = Progressbar(self, orient=HORIZONTAL, length=100, mode='indeterminate')

    def traitement(self):
        def real_traitement():
            self.progress.grid(row=1,column=0)
            self.progress.start()
            time.sleep(5)
            self.progress.stop()
            self.progress.grid_forget()

            self.btn['state']='normal'

        self.btn['state']='disabled'
        threading.Thread(target=real_traitement).start()


if __name__ == '__main__':
    app = MonApp()
    app.mainloop()
wovano
  • 3,264
  • 4
  • 18
  • 43
xmcp
  • 2,842
  • 1
  • 23
  • 36
  • thanks for the answer. I don't want to show the progresse bar before the user click on the EXE button... Is that i don't understand, how to show the progressbar went you are in -> def sous_ens... Went i try to put progress bar in the code of the fonction sous_ens, it appear only went i get off the fonction... – j666 Nov 18 '15 at 20:53
  • 1
    Just `pack`(or `grid`) the progress bar instance when you want to show it and use `pack_forget` (or `grid_forget`) to hide it. – xmcp Nov 18 '15 at 23:06
  • Thanks a lot. Put i don't catch how to... i put some simple code... How can i put a progress bar went i start the traitement fonction... – j666 Nov 21 '15 at 00:13
  • 2
    your code is almost correct. just use `threading` library and run `traitement` in another thread. see my updated answer. – xmcp Nov 21 '15 at 04:11
  • Just as a side note, this uses the ttk button instead of the tkinter button, since you import ttk after, and it overides the tkinter button class. – Artemis May 02 '18 at 19:09
  • This seems super simple, but I cannot solve where to put the code that should be running while the progress bar is working. Say I just want to write in a loop to the console while the progress bar is running, like this: [[ for i in range(0,200): print(i)]] Where would I put that in the example above for it to run while the progress bar is displayed and working? Thank you! – Korzak Jun 22 '18 at 21:15
  • My thread starts from outside the main class. And there it sows progressBar is not defined. Anyway to figure out that. – Naazneen Jatu Apr 06 '20 at 13:33
  • This answer uses `tkinter` widgets from the second thread which you shouldn't do. Most of the time `tkinter` doesn't allow you to access widgets if you aren't in the thread where the original `tk.Tk()` has been created. – TheLizzard Apr 08 '21 at 13:04
2

For all the GUI elements to modify themselves (in your case, for the progress bar to move) the execution must hit app.mainloop().

In your case, def traitement(self): starts and then stops the progressbar without hitting the mainloop, so it fails to visibly reflect the intended progressbar movement on the GUI. The catch here is, when the execution hits mainloop, progressbar is configured to 'stop' state.

Hence, it is a good idea to execute time consuming activities on a different Thread as shown by @xmcp

However, if you DO NOT want to use threading, you can use the after method to achieve what you want:

def stop_progressbar(self):
    self.progress.stop()

def traitement(self):
    self.progress.grid()
    self.progress.start()
    self.after(15000, self.stop_progressbar) 
    ## Call Just like you have many, many code lines...

The above code used self.after() method which will execute the stop_progressbar method to stop after 15 seconds, instead of time.sleep() which blocks the mainthread.

MightyInSpirit
  • 125
  • 1
  • 1
  • 8
  • You don't have to have `mainloop` in a tkinter app. You can use a while true loop of updating the window... it also works (except there is an error when you close the window) – YJiqdAdwTifMxGR Dec 28 '20 at 14:17