0

I'm implementing after() method to my code after finding out tkinter windows are not multithread-friendly. Back then I couldn't pop up my Toplevel with threading.Thread(), but seems like after() method is not working either. When you pressed the button, the Toplevel window (splash_screen) still gets laggy, you can't ever move nor resize it and it eventually freezes. I'm not sure why does it behave like this? Any ideas?

Thanks in advance!

import time
from tkinter import *
from PIL import Image, ImageTk


def splash_screen():
    global is_top_level_ready
    is_top_level_ready = False

    splash_screen = Toplevel(screen)

    frame = Frame(splash_screen, width=427, height=250)
    frame.place(x=0, y=0)

    label1 = Label(splash_screen, text='Please wait...')
    label1.place(x=122, y=120)

    image_a = ImageTk.PhotoImage(Image.open('dot_light.png'))
    image_b = ImageTk.PhotoImage(Image.open('dot_dark.png'))

    while True:
        if is_top_level_ready == False:
            l1 = Label(splash_screen, image=image_a).place(x=180, y=165)
            l2 = Label(splash_screen, image=image_b).place(x=215, y=165)
            l3 = Label(splash_screen, image=image_b).place(x=250, y=165)
            l4 = Label(splash_screen, image=image_b).place(x=285, y=165)
            splash_screen.update_idletasks()
            time.sleep(0.2)

            l1 = Label(splash_screen, image=image_b).place(x=180, y=165)
            l2 = Label(splash_screen, image=image_a).place(x=215, y=165)
            l3 = Label(splash_screen, image=image_b).place(x=250, y=165)
            l4 = Label(splash_screen, image=image_b).place(x=285, y=165)
            splash_screen.update_idletasks()
            time.sleep(0.2)

            l1 = Label(splash_screen, image=image_b).place(x=180, y=165)
            l2 = Label(splash_screen, image=image_b).place(x=215, y=165)
            l3 = Label(splash_screen, image=image_a).place(x=250, y=165)
            l4 = Label(splash_screen, image=image_b).place(x=285, y=165)
            splash_screen.update_idletasks()
            time.sleep(0.2)

            l1 = Label(splash_screen, image=image_b).place(x=180, y=165)
            l2 = Label(splash_screen, image=image_b).place(x=215, y=165)
            l3 = Label(splash_screen, image=image_b).place(x=250, y=165)
            l4 = Label(splash_screen, image=image_a).place(x=285, y=165)
            splash_screen.update_idletasks()
            time.sleep(0.2)

        else:
            splash_screen.destroy()
            break

    the_toplevel = Toplevel(screen)
    the_toplevel.minsize(width=650, height=415)

def checkloop():
    global is_splash_screen_ready
    if is_splash_screen_ready:
        splash_screen()
    else:
        screen.after(100, checkloop)

def splash_is_go():
    global is_splash_screen_ready
    is_splash_screen_ready = True


screen = Tk()

global is_splash_screen_ready
is_splash_screen_ready = False

button = Button(screen, text="Button", command=splash_is_go)
button.pack(pady=30, padx=30)

screen.after(100, checkloop)
screen.mainloop()

Image sources:

Dot Light Dot Dark

duruburak
  • 181
  • 14
  • 1
    Any time you call `sleep`, it’s impossible for tkinter to process any events. You should never call it in the GUI thread. – Bryan Oakley Jan 11 '23 at 21:46
  • @BryanOakley What can I use instead? – duruburak Jan 11 '23 at 21:48
  • @duruburak notice how the `.after` method takes in a time (in milliseconds) and a function as it's 2 main parameters. It's for cases like this. Basically you can use `.after(200, )` to call the function after 0.2 seconds. For more info look at [this](https://stackoverflow.com/a/459131/11106801) – TheLizzard Jan 11 '23 at 22:01
  • @TheLizzard I already used it. – duruburak Jan 11 '23 at 22:06

2 Answers2

3

Using .after() in this way does not make the while loop (and time.sleep()) non-blocking. You need to use .after() to replace the while loop and time.sleep():

import tkinter as tk
from PIL import ImageTk

def splash_screen():
    splash_win = tk.Toplevel(screen)

    frame = tk.Frame(splash_win)
    frame.pack(padx=100, pady=50)

    label1 = tk.Label(frame, text='Please wait...')
    label1.pack(pady=(0,10))

    image_light = ImageTk.PhotoImage(file='dot_light.png')
    image_dark = ImageTk.PhotoImage(file='dot_dark.png')

    dots = []
    for i in range(4):
        dots.append(tk.Label(frame, image=image_dark if i else image_light))
        dots[-1].pack(side="left")

    def update(i=0):
        dots[i].config(image=image_dark)
        i = (i + 1) % 4
        dots[i].config(image=image_light)
        splash_win.after(200, update, i)

    splash_win.after(200, update) # start the update loop
    splash_win.grab_set() # make it like a modal window

screen = tk.Tk()
button = tk.Button(screen, text="Button", command=splash_screen)
button.pack(pady=30, padx=30)
screen.mainloop()
acw1668
  • 40,144
  • 5
  • 22
  • 34
1

To resolves your problem I put the popup in a only class started by a threading.Thread() and to avoid the _tkinter.TclError: bad window path name ".!toplevel" error I put the try: command in line 32.

The __init__ runs the popup and do the animation in a funtion in the class that is started in a thread. The edited code:

import threading # For threads
import time
from tkinter import *
from PIL import Image, ImageTk

class App: # The class 
    def __init__(self):
        global is_top_level_ready
        is_top_level_ready = False


        self.splash_screen = Toplevel(screen) # <---

        frame = Frame(self.splash_screen, width=427, height=250)
        frame.place(x=0, y=0)

        label1 = Label(self.splash_screen, text='Please wait...')
        label1.place(x=122, y=120)

        image_a = ImageTk.PhotoImage(Image.open('dot_light.png'))
        image_b = ImageTk.PhotoImage(Image.open('dot_dark.png'))

        App.splash_screen(self, image_a, image_b) # Runs animation



    def splash_screen(self, image_a, image_b):
        while True:
            if is_top_level_ready == False:
                try: # <---
                    l1 = Label(self.splash_screen, image=image_a).place(x=180, y=165)
                    l2 = Label(self.splash_screen, image=image_b).place(x=215, y=165)
                    l3 = Label(self.splash_screen, image=image_b).place(x=250, y=165)
                    l4 = Label(self.splash_screen, image=image_b).place(x=285, y=165)
                    self.splash_screen.update_idletasks()
                    time.sleep(0.2)

                    l1 = Label(self.splash_screen, image=image_b).place(x=180, y=165)
                    l2 = Label(self.splash_screen, image=image_a).place(x=215, y=165)
                    l3 = Label(self.splash_screen, image=image_b).place(x=250, y=165)
                    l4 = Label(self.splash_screen, image=image_b).place(x=285, y=165)
                    self.splash_screen.update_idletasks()
                    time.sleep(0.2)

                    l1 = Label(self.splash_screen, image=image_b).place(x=180, y=165)
                    l2 = Label(self.splash_screen, image=image_b).place(x=215, y=165)
                    l3 = Label(self.splash_screen, image=image_a).place(x=250, y=165)
                    l4 = Label(self.splash_screen, image=image_b).place(x=285, y=165)
                    self.splash_screen.update_idletasks()
                    time.sleep(0.2)

                    l1 = Label(self.splash_screen, image=image_b).place(x=180, y=165)
                    l2 = Label(self.splash_screen, image=image_b).place(x=215, y=165)
                    l3 = Label(self.splash_screen, image=image_b).place(x=250, y=165)
                    l4 = Label(self.splash_screen, image=image_a).place(x=285, y=165)

                    self.splash_screen.update_idletasks()
                    time.sleep(0.2)
                except: # <---
                    break
            else:
                self.splash_screen.destroy()
                break

def checkloop():
    global is_splash_screen_ready
    if is_splash_screen_ready:
        run = App # <----
        threading.Thread(target=run).start() # Runs the popup
    else:
        screen.after(100, checkloop)

def splash_is_go():
    global is_splash_screen_ready
    is_splash_screen_ready = True


screen = Tk()

global is_splash_screen_ready
is_splash_screen_ready = False

button = Button(screen, text="Button", command=splash_is_go)
button.pack(pady=30, padx=30)

screen.after(100, checkloop)
screen.mainloop()
  • Thank you, this one works too. But as far as I know, using `threading.Thread()` isn't recommended for tkinter windows. – duruburak Jan 12 '23 at 02:35