-1

I am trying to create a 'loading' screen text where each second I want to change the tkinter label as follows:

Loading.
Loading..
Loading...

With each string replacing the previous one. My code kinda works, but it only displays the first and last label:

import sys
from tkinter import *
import time

root = Tk()
root.wm_title('test')
root.geometry("500x300")

my_label = Label(root, text="Loading.")
my_label.grid(row=0, column=0, columnspan=2)
time.sleep(1)
 
def loading():
    x = 1
    while x < 10: 
        my_label['text'] = "Loading." 
        time.sleep(1)
        my_label['text'] ="Loading.."
        time.sleep(1)
        my_label['text'] ="Loading..."
        #frame.pack(fill="both", expand=True)
        x = x+1
Button(root, text = """test
    """,command = loading).grid(row=2, column=1, columnspan=2)
root.mainloop()

How can I get this to update the label each second?

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
user3486773
  • 1,174
  • 3
  • 25
  • 50
  • 1
    `sleep(1)` does exactly what it says: it puts the whole application to sleep. While sleeping, it can't update the window. There are many questions on this site about using `sleep`. Do some research on that, and on using tkinter's `after` method. – Bryan Oakley Aug 16 '22 at 18:05
  • 1
    Just in case you are unaware, `ttk` has a `Progressbar` widget that you can implement if you are interested [look here](https://www.pythontutorial.net/tkinter/tkinter-progressbar/). Otherwise Bryan's comment has you covered. – Rory Aug 16 '22 at 18:10
  • If you want to append text, then you want `+=`, e.g. `my_label['text'] += "Loading.."`. Currently your code does replace the whole label therefore only looks like you have the first/last one. – OneCricketeer Aug 16 '22 at 18:18
  • Does this answer your question? [How can I schedule updates (f/e, to update a clock) in tkinter?](https://stackoverflow.com/questions/2400262/how-can-i-schedule-updates-f-e-to-update-a-clock-in-tkinter) – Mike - SMT Aug 16 '22 at 18:20
  • @OneCricketeer **If you want to append text, then you want +=**. This is not the issue here. He is not appending text he just needs to refactor his code to use AFTER instead of SLEEP. The issue has absolutely nothing to do with `+=` and everything to do with sleep causing the application to freeze and that is why all you see is the 1st and last. – Mike - SMT Aug 16 '22 at 18:22
  • @Mike-SMT Yes, you are correct. My comment was more related to - Show `Loading`. Then the loop could cycle through appending `['.', '..', '...']` onto that. – OneCricketeer Aug 16 '22 at 18:26

2 Answers2

1

Seeing that an issue in tkinter with sleep() has been asked many many times on here I want to point out that a little more digging before posting a question would have solved your problem.

That said I have refactored your code to show a working example using after() since that is the correct method to use in Tkinter.

Note that I have changed your import as well for best practice. Since list are one of my favorite solutions to anything related to Tkinter I will use a list to build a looping function that will terminate after a counter has reached zero.

In your case you can change the logic any way that makes since for your tool but this is simple to show how the logic would work until some value it met.

The after method is used to set a time to call the function and the lambda is used to prevent the call being instant as well as providing the values for the next loop.

We also add in a change state for the button so you do not mistakenly press the button again causing the function to be called again with 2 counters.

By writing it this way with after you wont see your application freeze.

import tkinter as tk

root = tk.Tk()
root.wm_title('test')
root.geometry("500x300")

my_label = tk.Label(root, text="Waiting")
my_label.grid(row=0, column=0, columnspan=2)

def loading(counter, ndex=0):
    my_label['state'] = 'disabled'
    load = ["", ".", "..", "..."]
    print(ndex, len(load)-1)
    if counter != 0:
        counter -= 1
        if ndex >= len(load)-1:
            my_label['text'] = f'Loading{load[ndex]}'
            ndex = 0
        else:
            my_label['text'] = f'Loading{load[ndex]}'
            ndex += 1
        root.after(1000, lambda c=counter, n=ndex: loading(c, n))
    else:
        my_label['text'] = 'Done'
        my_label['state'] = 'normal'


tk.Button(root, text="""test""", command=lambda: loading(10)).grid(row=2, column=1, columnspan=2)
root.mainloop()

Result: enter image description here

Mike - SMT
  • 14,784
  • 4
  • 35
  • 79
  • @OneCricketeer added in some appending to my example for good measure :) – Mike - SMT Aug 16 '22 at 18:51
  • Can be shortened to: `root.after(1000, loading, counter, ndex)` – Delrius Euphoria Aug 16 '22 at 18:56
  • @DelriusEuphoria Good point. Miss-read that at first. I see that you mean the after method would be shorter. – Mike - SMT Aug 16 '22 at 18:57
  • @DelriusEuphoria Yes it is an option to use that method of writing after. I am so use to using LAMBDA to solve looping problems its mostly habit at this point. Your method is cleaner and does not require understanding how lambda works. – Mike - SMT Aug 16 '22 at 19:03
  • Kind of, and also instead of making a list and indexing it, I'd rather use string multiplication ~ `'Loading' + '.' * ndex`. And also since you are doing a same thing inside `if ndex >= len(load)-1:` and `else`, why don't you move it outside the `if`. Just tiny enhancements – Delrius Euphoria Aug 16 '22 at 19:06
  • I did search quite a bit before posting, but wasn't aware of after() until you said something. A lot of what I found was sleep vs wait. I also appreciate the answer, but this is just too much text for a few dots to appear and thought it would be simpler. I actually went with the progress bar suggested by @Rory as it was just easier and better looking. My tkinter ignorance is where I suffer. – user3486773 Aug 16 '22 at 19:29
0

You can also use thread aproach that your main thread will not freeze. I used ThreadPoolExecutor class instead of Thread class, because we don't need to create a new thread when every button press. I didn't do any prevention that button can be pressed two times mistakenly, you can modify this according to your situation

import sys
from tkinter import *
import time
from concurrent.futures import ThreadPoolExecutor

root = Tk()
root.wm_title('test')
root.geometry("500x300")

my_label = Label(root, text="Loading.")
my_label.grid(row=0, column=0, columnspan=2)
time.sleep(1)

executor=ThreadPoolExecutor(max_workers=1)

def loading():

    def _loading():
        x = 1
        while x < 10: 
            my_label['text'] = "loading." 
            time.sleep(1)
            my_label['text'] ="loading.."
            time.sleep(1)
            my_label['text'] ="loading..."
            time.sleep(1)
            #frame.pack(fill="both", expand=True)
            x = x+1
    executor.submit(_loading)

Button(root, text = "test",command = loading).grid(row=2, column=1, columnspan=2)
root.mainloop()
Veysel Olgun
  • 552
  • 1
  • 3
  • 15