Python tkinter update_idletasks is blanking the window
Question: [This has been fairly significantly edited in the light of comments to the
original post, and to make the context - 2 modules - clearer and to summarise
what I think is the key underlying issue. The code is also updated. I have a
working version but am not at all sure its done the right way.] (Disclaimer
... Im learning Tkinter as I go along!)

Im attempting to display a progress bar while an app is running (eg walking a
music library folder tree but that doesnt matter here).

I'd like to implement this as a class in a separate module from the main app
so I can use it elsewhere (also the app itself is actually in 2 modules).

For that reason, and also because I don't want to upset the app's main window
design Id like the progress bar to appear in a separate window.

I've tried this two ways ... my own crudely drawn progress bar using a text
widget, and - once I discovered it - ttk.Progressbar. Im now focusing on using
ttk.Progressbar.

Note however I had essentially the same problem with either approach, which is
getting the contents of the progress window to display properly without
preventing control reverting back to the calling module.

My class for this (ProgressBar) has methods to start, update, and stop the
progress bar. As I understand it there are three ways to force refreshing of
the status window in the class methods. All three seem to have drawbacks.

  * root.master.mainloop() keeps control in the progress window and the app stops executing. This basically defeats the purpose.
  * root.master.update_idletasks() gives control back to the calling app but the status window is blanked out. Also defeats the purpose, for a different reason.
  * root.master.update() seems to work perfectly, the status window is updated with visible contents and control goes back to the calling app. HOWEVER Ive read in several places this is a dangerous method to use.

So the basic questions are - What is the correct way to force the window to
update (eg the Set method); and why is update_idletasks() blanking the
progress window.

I believe the following code reflects the suggestions made but I have adapted
it to reflect the intended import class.

    
    
    # dummy application importing the StatusBar class.
    # this reflects app is itslef using tkinter
    
    from ProgressBar12 import ProgressBar
    
    import tkinter as Tk
    import time
    import os
    
    def RunAppProcess():
    
        print('App running')
        Bar = ProgressBar(tkroot)      # '' arg to have ProgressBar create its tkroot
        Bar.start('Progress...', 0)   # >0 determinate (works) / 0 for indeterminate (doesnt!)
    
        print('starting process')
        # this simulates some process, (eg for root, dirs, files = os.walk(lib))
        for k in range(10):
            Bar.step(5)                # (should be) optional for indeterminate
            time.sleep(.2)
        Bar.stop('done')               # '' => kill the window; or 'message' to display in window
    
    def EndAppProcess():
        tkroot.withdraw()
        tkroot.destroy()
    
    # Application init code, the application is using tkinter
    # (should probably be in an init procedure etc, but this will serve)
    tkroot = Tk.Tk()
    tkroot.title("An Application")
    tkroot.geometry("100x100")
    tkroot.configure(bg='khaki1')
    # a 2 button mini window:  [Start] and [Quit]
    Tk.Button(tkroot, text='Start', bg='orange', command=RunAppProcess).grid(sticky=Tk.W)
    Tk.Button(tkroot, text="Quit", bg="orange", command=EndAppProcess).grid(sticky=Tk.W)
    tkroot.mainloop()
    

ProgressBar Module

    
    
    # determinate mode
    import tkinter as Tk
    import tkinter.font as TkF
    from tkinter import ttk
    import time
    
    # print statements are for tracing execution
    # changes from the sample code previsouly given reflect:
    # - suggestions made in the answer and in comments
    # - to reflect the actual usage with the class imported into a calling module rather than single module solution
    # - consistent terminology (progress not status)
    # - having the class handle either determinate or indeterminate progress bar
    
    
    class ProgressBar():
    
        def __init__(self, root):
            print('progress bar instance init')
            if root == '':
                root = tkInit()
            self.master=Tk.Toplevel(root)
            # Tk.Button(root, text="Quit all", bg="orange", command=root.quit).grid()   A bit rude to mod the callers window
            self.customFont2 = TkF.Font(family="Calibri", size=12, weight='bold')
            self.customFont5 = TkF.Font(family="Cambria", size=16, weight='bold')
            self.master.config(background='ivory2')
            self.create_widgets()
            self.N = 0
            self.maxN = 100 # default for %
    
        def create_widgets(self):
    
            self.msg = Tk.Label(self.master, text='None', bg='ivory2', fg='blue4') #,                           font=self.customFont2)
            self.msg.grid(row=0, column=0, sticky=Tk.W)
    
            self.bar = ttk.Progressbar(self.master, length=300, mode='indeterminate')
            self.bar.grid(row=1, column=0, sticky=Tk.W)
    
            #self.btn_abort = Tk.Button(self.master, text=' Abort ', command=self.abort, font=self.customFont2, fg='maroon')
            #self.btn_abort.grid(row=2,column=0, sticky=Tk.W)
            #self.master.rowconfigure(2, pad=3)
    
            print('progress bar widgets done')
    
        def start(self, msg, maxN):
            if maxN <= 0:
                #indeterminate
                self.msg.configure(text=msg)
                self.bar.configure(mode='indeterminate')
                self.maxN = 0
                self.bar.start()
                self.master.update()
    
            else: # determinate
                self.msg.configure(text=msg)
                self.bar.configure(mode='determinate')
                self.maxN = maxN
                self.N = 0
                self.bar['maximum'] = maxN
                self.bar['value'] = 0
    
        def step(self, K):
            #if self.maxN == 0: return    # or raise error?
            self.N = min(self.maxN, K+self.N)
            self.bar['value'] = self.N
            self.master.update()         # see  set(..)
    
        def set(self, K):
            #if self.maxN == 0: return
            self.N = min(self.maxN, K)
            self.bar['value'] = self.N
            #self.master.mainloop()         # <<< calling module does not regain control.  Pointless.
            #self.master.update_idletasks   # <<< works, EXCEPT statusbar window is blank! Also pointless.  But calling module regains control
            self.master.update()            # <<< works in all regards, BUT I've read this is dangerous.
    
    
        def stop(self, msg):
            print('progress bar stopping')
            self.msg.configure(text=msg)
            if self.maxN <= 0:
                self.bar.stop()
            else:
                self.bar['value'] = self.maxN
                #self.bar.stop()
            if msg == '':
                self.master.destroy()
            else: self.master.update()
    
        def abort(self):
            # eventually will raise an error to the calling routine to stop the process
            self.master.destroy()
    
    def tkInit():
        print('progress bar tk init')
        tkroot = Tk.Tk()
        tkroot.title("Progress Bar")
        tkroot.geometry("250x50")
        tkroot.configure(bg='grey77')
        tkroot.withdraw()
        return tkroot
    
    if (__name__ == '__main__'):
        print('start progress bar')
        tkroot = tkInit()
        tkroot.configure(bg='ivory2')
        Bar = ProgressBar(tkroot)
        Bar.start('Demo', 10)
        for k in range(11):
            Bar.set(k)
            time.sleep(.2)
        Bar.stop('done, you can close me')
    
    else:
        # called from another module
        print('progress bar module init. (nothing) done.')
    

This is based on the first of the solutions in the answer; as an alternative I
will try the second using after() .... I first have to understand exactly what
that does.


Answer: First, you don't call mainloop() anywhere. The following code displays a
moving progress bar until you hit the abort button. The for() loop in your
code above serves no purpose as it does nothing except stop program execuption
for 0.3*20 seconds. If you want to update the progress bar yourself, then see
the 2nd example and how it uses "after" to call an update function until the
progress bar completes. And note that everything associated with it is
contained in the class, which is one of the reasons you would use a class. You
could also call the update function from outside the class, but the update
function would still be in the same class that created the progress bar.

    
    
    import Tkinter as Tk
    import tkFont as TkF
    import ttk
    import time
    
    class StatusBar():
    
        def __init__(self, root):
            self.master=Tk.Toplevel(root)
            Tk.Button(root, text="Quit all", bg="orange", command=root.quit).grid()
            self.customFont2 = TkF.Font(family="Calibri", size=12, weight='bold')
            self.customFont5 = TkF.Font(family="Cambria", size=16, weight='bold')
            self.master.config(background='ivory2')
            self.ctr=0
            self.create_widgets()
    
        def create_widgets(self):
    
            self.msg = Tk.Label(self.master, text='None', bg='ivory2', fg='blue4',
                               font=self.customFont2, width=5)
            self.msg.grid(row=0, column=0, sticky=Tk.W)
    
            self.bar = ttk.Progressbar(self.master, length=300, mode='indeterminate')
            self.bar.grid(row=1, column=0, sticky=Tk.W)
    
            self.btn_abort = Tk.Button(self.master, text=' Abort ', command=self.abort, font=self.customFont2, fg='maroon')
            self.btn_abort.grid(row=2,column=0, sticky=Tk.W)
            self.master.rowconfigure(2, pad=3)
    
            print('widgets done')
    
        def Start(self, msg):
            self.msg.configure(text=msg)
            self.bar.start()
        def Stop(self, msg):
            self.msg.configure(text=msg)
            self.bar.stop()
    
        def abort(self):
            # eventually will raise an error to the calling routine to stop the process
            self.master.destroy()
    
    if (__name__ == '__main__'):
        print('start')
        tkroot = Tk.Tk()
        tkroot.title("Status Bar")
        tkroot.geometry("500x75")
        tkroot.configure(bg='ivory2')
        Bar = StatusBar(tkroot)
        Bar.Start('Demo')
        tkroot.mainloop()
    

Uses after() to update the progress bar

    
    
    try:
        import Tkinter as tk     ## Python 2.x
    except ImportError:
        import tkinter as tk     ## Python 3.x
    
    import ttk
    
    class TestProgress():
        def __init__(self):
            self.root = tk.Tk()
            self.root.title('ttk.Progressbar')
    
            self.increment = 0
            self.pbar = ttk.Progressbar(self.root, length=300)
            self.pbar.pack(padx=5, pady=5)
    
            self.root.after(100, self.advance) 
            self.root.mainloop()
    
        def advance(self):
            # can be a float
            self.pbar.step(5)
            self.increment += 5
            if self.increment < 100:
                self.root.after(500, self.advance) 
            else:
                self.root.quit()
    
    
    TP=TestProgress()
    

