1

Is there any way to create a loading screen in tkinter?

Here's the code:

from tkinter import *

root = Tk()

text = Text(root , width = 65 , height = 20 , font = "consolas 14")
text.pack()

text.insert('1.0' , "hello\n"*5000000)

mainloop()

Here, the main window takes some time to pop up, so I would like to create a temporary window that tells the user that some process is going on.

To do this, I did something like this:

from tkinter import *

root = Tk()

temp_win = Toplevel()
message_label = Label(temp_win , text = "Loading.. Please wait")
message_label.pack()

text = Text(root , width = 65 , height = 20 , font = "consolas 14")
text.pack()

text.insert('1.0' , "hello\n"*5000000)

mainloop()

But here, both the windows only pop up only when the mainloop function is called, which is of no use.

My goal is to show the temporary window before the main window pops up.

Is there any way to achieve this in tkinter?

It would be great if anyone could help me out.

Lenovo 360
  • 569
  • 1
  • 7
  • 27
  • Why is ur app slow? – Daniyal Warraich Apr 14 '21 at 17:54
  • @DaniyalWarraich: I made it slow, just for demonstration purposes. As you can see in this line `text.insert('1.0' , "hello\n"*5000000)`, I'm inserting so many lines into the text widget, which makes the app slow. – Lenovo 360 Apr 14 '21 at 18:01
  • Main window takes time to pop up? – Delrius Euphoria Apr 14 '21 at 18:06
  • @CoolCloud: Yes, the line `text.insert('1.0' , "hello\n"*5000000)` makes the window take time to pop up. – Lenovo 360 Apr 14 '21 at 18:07
  • 1
    Does this answer your question? [Tkinter Show splash screen and hide main screen until \_\_init\_\_ has finished](https://stackoverflow.com/questions/38676617/tkinter-show-splash-screen-and-hide-main-screen-until-init-has-finished) – 001 Apr 14 '21 at 18:13
  • To show real delay, use `time.sleep(5)`. – Delrius Euphoria Apr 14 '21 at 18:17
  • @CoolCloud: Well, `time.sleep()` adds a delay to the program. But in my real code, the original process itself takes time. – Lenovo 360 Apr 14 '21 at 18:20
  • Let me explain my real situation. I have imported many modules in my original program, so it takes time to import all of them. I would like to show a loading screen at the same time when the modules are getting imported, so that the user can know that some process is going on. Threading does not help, because calling `mainloop` from a different thread causes problems. – Lenovo 360 Apr 14 '21 at 18:25
  • 1
    Give the below solution a try, which does use `threading`, but does not communicate with the main tkinter directly. – Delrius Euphoria Apr 14 '21 at 18:35

3 Answers3

2

Let the tkinter be in main code, but make a function for the splash screen, so its like:

import threading
from tkinter import *

started = False
def pop():
    new = Tk()

    l = Label(new,text='Loading')
    l.pack()

    new.protocol('WM_DELETE_WINDOW',lambda: new.destroy() if started else False) # Close the window only if main window has shown up
    new.mainloop()

threading.Thread(target=pop).start() # Use this before the heavy process starts

root = Tk()

text = Text(root , width = 65 , height = 20 , font = "consolas 14")
text.pack()

text.insert('1.0' , "hello\n"*5000000) # If you want to see real delay use time.sleep(5)

started = True
root.mainloop()

This way there is no relation between the first two instances of Tk directly, using a Toplevel would require a Tk instance to be made first.

Flag started is used just to ensure that the splash screen cannot be closed before the main window pops up.

Delrius Euphoria
  • 14,910
  • 3
  • 15
  • 46
  • In this code `new.mainloop()` waits until all windows have been closed. This means that it can fire events even though it is in a different thread. The only solution that shouldn't cause problems is to move all of the non-GUI stuff in the thread and leave everything else in the main thread. – TheLizzard Apr 14 '21 at 19:00
  • @TheLizzard That would mean what ever is after `new.mainloop()` would not be executed? – Delrius Euphoria Apr 14 '21 at 21:19
  • Correct. `.mainloop()` is a blocking call until all `tkinter.Tk` windows are closed. Try it out - add `print("!")` just after `new.mainloop()` and see if it is executed. – TheLizzard Apr 14 '21 at 21:32
  • @TheLizzard It did get executed after I closed the splash screen. – Delrius Euphoria Apr 14 '21 at 21:46
  • 1
    My bad I forgot that that only happens when it is called from an event/`.after` script. So your code should be fine. – TheLizzard Apr 14 '21 at 21:51
  • Hey @CoolCloud, I ran into a small problem. When I try to destroy the loading window once the heavy process has finished, the program stops responding. Do you have any solution for this? Any help would be greatly appreciated. – Lenovo 360 May 27 '21 at 14:56
  • @Lenovo360 The program stopped responding mostly because of the heavy process happening in the same thread – Delrius Euphoria May 27 '21 at 15:02
  • @CoolCloud: Well, the program does respond when I withdraw the window or don't do anything with it. It only stops responding when I try to destroy it. – Lenovo 360 May 27 '21 at 15:13
  • @Lenovo360 Maybe ask a new question about this with detailed explanation – Delrius Euphoria May 27 '21 at 15:23
  • @CoolCloud: I experimented with this problem, and I think this happens because a `Tk()` instance created in one thread cannot be controlled with another. I fixed this problem by creating a nested function that get's called recursively and closes the loading window once the value of a variable is set to true. – Lenovo 360 May 31 '21 at 08:05
  • @Lenovo360 Yes, two `tkinter` cannot communicate between threads. – Delrius Euphoria May 31 '21 at 09:32
1

tkinter doesn't allow you to call it from multiple threads (it might crash without any errors). This means that you can't do any GUI stuff in threads other than the main one/where you create your Tk(). Also there are some bugs with how _tkinter communicates with tcl. Keeping that in mind this is what I have:

from time import sleep
import tkinter as tk
from threading import Thread

def non_gui_stuff():
    # We are going to do some work
    sleep(3)


t = Thread(target=non_gui_stuff, daemon=True)
t.start()

root = tk.Tk()
# Hide the main window until the non-GUI stuff is done
root.withdraw()

# Create the loading screen
loading_screen = tk.Toplevel(root)
loading_label = tk.Label(loading_screen, text="Loading")
loading_label.pack()

# While the thread is alive
while t.is_alive():
    # Update the root so it will keep responding
    root.update()
print("Non-GUI thread is done")

loading_screen.destroy()
# Show the main window
root.deiconify()
root.focus_force()

root.mainloop()
TheLizzard
  • 7,248
  • 2
  • 11
  • 31
0

I believe you could do this with threading.

import threading
from tkinter import *

root = Tk()

def loading_screen():
    temp_win = Toplevel()
    message_label = Label(temp_win , text = "Loading.. Please wait")
    message_label.pack()

def main():
    text = Text(root , width = 65 , height = 20 , font = "consolas 14")
    text.pack()

    text.insert('1.0' , "hello\n"*5000000)

a = threading.Thread(target=loading_screen)
b = threading.Thread(target=main)
a.start()
b.start()
mainloop()
Zachary
  • 532
  • 3
  • 12
  • This dosent answers the question. Also have you considered that you creating the instance of `Tk()` in the main thread and construc the children in two different threads, while the children arent shown till the process is finished? This isnt a solution its the same problem in a different way. – Thingamabobs Apr 14 '21 at 18:14
  • this works fine on my computer. When I run the code the loading screen pops up with text even while the other window is loading – Zachary Apr 14 '21 at 18:15
  • Also using tkinter from multiple threads might crash tkinter so please don't do it – TheLizzard Apr 14 '21 at 18:15
  • ok thank you @TheLizzard for explaining that to me – Zachary Apr 14 '21 at 18:16
  • @TheLizzard But I think, that if the second `Tk` thread instance does not interfere with the first instance then its fine? – Delrius Euphoria Apr 14 '21 at 18:18
  • 1
    @CoolCloud Well... It's complicated but I think that it still might crash because the `mainloop()` (btw `root.mainloop()` will have the same effect) waits for all windows to close which means that events will be called from the main thread. And that is a different thread from the one where the `Tk()` was created. Also some times tcl handles things like [this](https://stackoverflow.com/a/67007762/11106801) weirdly. – TheLizzard Apr 14 '21 at 18:53