Search code examples
pythonfor-loopuser-interfacetkintersleep

How can I replace a variable using a "for" loop and a time delay? I'm using Python and Tkinter


I want to create a sort of "loading" window for a function in one of my apps. I've tried the progress bar, but I wanted to try this instead and see how it compares. The idea is to have a new window open when a button is clicked. The new window will have the word "Loading", which will then have a full stop "." appended to it after a short time delay. I've used the time.sleep() function, but it is not quite behaving as it should.

I'll include the code below:

from tkinter import *
from tkinter.ttk import *
from tkinter import Button, Tk, HORIZONTAL
import time

def new_window():
    variable_text = "Loading"
    new_windy = Toplevel(main_window)
    new_windy.title("New_Windy")
    new_windy.geometry("200x200")
    for i in range(10):
        time.sleep(0.3)
        new_windy.update_idletasks()
        variable_text += "."
        label = Label(new_windy, text=variable_text)
        label.pack()

main_window = Tk()
main_window.title("AWESOME_SAUCE")
main_window.geometry('600x600')


button = Button(main_window, text="click_me", command=new_window)
button.pack()

main_window.mainloop()

The code above creates the new window at the click of the button, but instead of replacing the word "loading" each iteration with an appended full stop ".", it just creates 10 versions of the word "loading" stacked on itself with successive numbers of full stops.


Solution

  • There are two ways I would do this. One is using a list of words and after() and the other would be adding dots to the end of the sentence.

    Method 1: To begin with, create a list that has all the transitional words:

    transition = ['Loading','Loading.','Loading..','Loading...','Loading....','Loading.....']
    

    and now you need to index this list and call each item on the label, like:

    count = 0 #a number to index the list
    def new_window():
        new_windy = Toplevel(main_window)
        new_windy.title("New_Windy")
        new_windy.geometry("200x200")
    
        def change():
            global count #globalize it
            rep = main_window.after(1000,change) #repeat the function every 1 second
            try: #to catch the index error
                label.config(text=transition[count]) #change the text with the list index
            except IndexError: #to get past the error showing up
                main_window.after_cancel(rep) #stop the repetition if no more items left to show
            count += 1 #increase the number by 1 over each repetition
            
        label = Label(new_windy, text=transition[count]) #initially set the text to first element
        label.pack()
    
        change() #call the function for the first time
    
    transition = ['Loading','Loading.','Loading..','Loading...','Loading....','Loading.....']
    

    Method 2: There is also another method like you did using += '.'. That would be like:

    def new_window():
        global text #globalize the main text
        
        new_windy = Toplevel(main_window)
        new_windy.title("New_Windy")
        new_windy.geometry("200x200")
    
        text = 'Loading' #set the main text 
        def change():
            global text #globalize the new text
            text += '.' #add . over each iteration
    
            rep = main_window.after(1000,change) #repeat the function every 1 second
    
            label.config(text=text) #change the text to new text
            if len(text) >= 7+5: #here 7 is the length of word 'Loading' and 5 is the maximum number of dots needed.
                main_window.after_cancel(rep) #stop repeating the function
    
        label = Label(new_windy, text=text) #set the main text at first
        label.pack()
    
        change() #call the function initially.
    

    Now to some explanation:

    but it is not quite behaving as it should.

    That is because time.sleep() messes with mainloop() causing the unresponsiveness. Hence the GUI freezes, so instead we have to use a after() that does not freeze the GUI. Not only time.sleep(), also while and for loops mess with mainloop() causing the GUI to be unresponsive.

    it just creates 10 versions of the word "loading" stacked on itself with successive numbers of full stops.

    That is because every time the code is run, a new label is created with the text "Loading", what you want to do is, create the text once, and use config() to change the text later.

    As a matter of fact your imports will cause you trouble later on, you are over writing tkinter widgets with widgets from ttk, so change you import statement and adjust the code accordingly, like:

    import tkinter as tk
    from tkinter import ttk
    

    Now if you want a tkinter widget use, tk.Label() or if you want a ttk widget, use ttk.Label() and so on.