how to show a loading message in tkinter

15,118

Solution 1

Have a look at the manual for widgets. w.wait_visibility(window) waits till the widget is visible. The 'normal' way of things (in all GUI toolkits) is to put all drawing commands, such as your label, in a waiting list, and do the actual drawing when there's time, prioritizing other events). From the page:

Wait for the given widget to become visible. This is typically used to wait until a new toplevel window appears on the screen. Like wait_variable, this method enters a local event loop, so other parts of the application will still work as usual.

An example for the use of wait_visibility comes from the test_widgets.py code, where setup waits for the widget to be really shown:

class WidgetTest(unittest.TestCase):
    """Tests methods available in every ttk widget."""

    def setUp(self):
        support.root_deiconify()
        self.widget = ttk.Button(width=0, text="Text")
        self.widget.pack()
        self.widget.wait_visibility()

Of course, the compare function does have to take some appreciable time - else the label will probably disappear before it can actually be seen on the screen. Your screen is redrawn 60 times per second, so if the comparing takes less than 16 ms, you'll probably not see anything.

EDIT: A better way to do this is actually using update_idletasks. Here's some code:

import tkinter as tk
import time

class SampleApp(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        self.frame = tk.Frame(self)
        self.frame.pack(side="top", fill = "both", expand=True)

        self.label = tk.Label(self, text = "Hello, world")
        button1 = tk.Button(self, text = "Start to do something",
                                  command = self.do_something)
        self.label.pack(in_=self.frame)
        button1.pack(in_=self.frame)

    def do_something(self):
        self.label.config(text = "Wait till I'm done...")
        self.label.update_idletasks()
        time.sleep(2)
        print ("end sleep")
        self.label.config(text = "I'm done doing...")

def main():
    app = SampleApp()
    app.mainloop()  
    return 0
    
if __name__ == '__main__':
    main()

The time.sleep in do_something simulates whatever you want to do. Click on the button to start the process.

Solution 2

This will make a popup with an indeterminate progress bar appear with a given message for as long as the given function is running.

from tkinter import *
import tkinter.ttk as ttk
import threading


# the given message with a bouncing progress bar will appear for as long as func is running, returns same as if func was run normally
# a pb_length of None will result in the progress bar filling the window whose width is set by the length of msg
# Ex:  run_func_with_loading_popup(lambda: task('joe'), photo_img)  
def run_func_with_loading_popup(func, msg, window_title = None, bounce_speed = 8, pb_length = None):
    func_return_l = []

    class Main_Frame(object):
        def __init__(self, top, window_title, bounce_speed, pb_length):
            print('top of Main_Frame')
            self.func = func
            # save root reference
            self.top = top
            # set title bar
            self.top.title(window_title)

            self.bounce_speed = bounce_speed
            self.pb_length = pb_length

            self.msg_lbl = Label(top, text=msg)
            self.msg_lbl.pack(padx = 10, pady = 5)

            # the progress bar will be referenced in the "bar handling" and "work" threads
            self.load_bar = ttk.Progressbar(top)
            self.load_bar.pack(padx = 10, pady = (0,10))

            self.bar_init()


        def bar_init(self):
            # first layer of isolation, note var being passed along to the self.start_bar function
            # target is the function being started on a new thread, so the "bar handler" thread
            self.start_bar_thread = threading.Thread(target=self.start_bar, args=())
            # start the bar handling thread
            self.start_bar_thread.start()

        def start_bar(self):
            # the load_bar needs to be configured for indeterminate amount of bouncing
            self.load_bar.config(mode='indeterminate', maximum=100, value=0, length = self.pb_length)
            # 8 here is for speed of bounce
            self.load_bar.start(self.bounce_speed)            
#             self.load_bar.start(8)            

            self.work_thread = threading.Thread(target=self.work_task, args=())
            self.work_thread.start()

            # close the work thread
            self.work_thread.join()


            self.top.destroy()
#             # stop the indeterminate bouncing
#             self.load_bar.stop()
#             # reconfigure the bar so it appears reset
#             self.load_bar.config(value=0, maximum=0)

        def work_task(self):
            func_return_l.append(func())


    # create root window
    root = Tk()

    # call Main_Frame class with reference to root as top
    Main_Frame(root, window_title, bounce_speed, pb_length)
    root.mainloop() 
    return func_return_l[0]

if __name__ == '__main__':
    import time
    def task(i):
        # The window will stay open until this function call ends.
        for x in range(10):
            print('hi: ' + i)
            time.sleep(.5) # Replace this with the code you want to run
        return "this is the func return"

    msg = 'running func...'        

    bounc_speed = 9
    pb_length = 200
    window_title = "Wait"

    r = run_func_with_loading_popup(lambda: task('joe'), msg, window_title, bounc_speed, pb_length)

    print('return of test: ', r)    
Share:
15,118
Jerin A Mathews
Author by

Jerin A Mathews

Android Developer and FOSS enthusiast.

Updated on June 04, 2022

Comments

  • Jerin A Mathews
    Jerin A Mathews about 2 years

    I am new to tkinter. I made the required dialog box. One of my function takes some time to process. So i want to show a "loading ... " message before that function starts executing.

    b1 = Button(text = "Compare",command = compare)
    b1.pack()
    

    when this button is clicked compare() function start executing. i want to show a loading message before that function starts. I tried using a label , such that i set a value to it on the starting of the compare() function. but it will only takes effect after the function finish executing.

    How can i do it? please help me..

  • bpops
    bpops about 4 years
    I would be interested to know how to implement this from another parent window, rather than making the indeterminate progress window root.
  • El pocho la pantera
    El pocho la pantera about 3 years
    It worked for me, but the main app remains opened. I had to add os._exit(1) to finish the app.