pythontkinteranimated-gif

Play an animated GIF in python with tkinter while loading


I'm trying to play a GIF and let it loop for 4 secs while loading a progress bar. I've tried so many combinations of solutions to no avail. Below is the closest I got. The loadingAnimation() function runs the progress bar well but I can't get the gif part together(M_95()). Ideally would love for the GIF to play in the middle of the screen at the same time the progress bar loads, then close the GIF window when it's finished.

import threading
import tkinter

root = tkinter.Tk()
frames = [PhotoImage(file='./myScripts/M-95.gif',format = 'gif -index %i' %(i)) for i in range(10)]

def M_95() :
    # Play GIF (file name = m95.gif) in a 320x320 tkinter window 
    # Play GIF concurrently with the loading animation below
    # Close tkinter window after play 
  
def loadingAnimation(process):
    while process.is_alive() :
        animation = ["[■□□□□□□□□□]","[■■□□□□□□□□]", "[■■■□□□□□□□]", "[■■■■□□□□□□]", "[■■■■■□□□□□]", "[■■■■■■□□□□]", "[■■■■■■■□□□]", "[■■■■■■■■□□]", "[■■■■■■■■■□]", "[■■■■■■■■■■]"]
        for i in range(len(animation)):
            sys.stdout.write("\r | Loading..." + animation[i % len(animation)])
            sys.stdout.flush()
            time.sleep(0.4)

loading_process = threading.Thread(target = M_95)
loading_process.start()

loadingAnimation(loading_process) 
loading_process.join()


Solution

  • First using while loop and time.sleep() in the main thread of a tkinter application is not recommended because it will block tkinter mainloop() and then cause the GUI not responding.

    Suggest to:

    import threading
    import tkinter
    import sys
    import time
    
    root = tkinter.Tk()
    frames = [tkinter.PhotoImage(file='./myScripts/M-95.gif', format='gif -index %i'%(i)) for i in range(10)]
    
    def center_window(win):
        win.wait_visibility() # make sure the window is ready
        x = (win.winfo_screenwidth() - win.winfo_width()) // 2
        y = (win.winfo_screenheight() - win.winfo_height()) // 2
        win.geometry(f'+{x}+{y}')
    
    def M_95(n=0, top=None, lbl=None):
        # Play GIF (file name = m95.gif) in a 320x320 tkinter window
        # Play GIF concurrently with the loading animation below
        # Close tkinter window after play
        global process_is_alive  # used in loadingAnimation()
        delay = 4000 // len(frames) # make one cycle of animation around 4 secs
        if n == 0:
            root.withdraw()
            top = tkinter.Toplevel()
            lbl = tkinter.Label(top, image=frames[0])
            lbl.pack()
            center_window(top)
            process_is_alive = True
        if n < len(frames)-1:
            lbl.config(image=frames[n])
            lbl.after(delay, M_95, n+1, top, lbl)
        else:
            top.destroy()
            root.deiconify()
            process_is_alive = False
    
    
    def loadingAnimation():
        animation = ["[■□□□□□□□□□]","[■■□□□□□□□□]", "[■■■□□□□□□□]", "[■■■■□□□□□□]", "[■■■■■□□□□□]", "[■■■■■■□□□□]", "[■■■■■■■□□□]", "[■■■■■■■■□□]", "[■■■■■■■■■□]", "[■■■■■■■■■■]"]
        i = 0
        while process_is_alive:
            sys.stdout.write("\r | Loading..." + animation[i % len(animation)])
            sys.stdout.flush()
            time.sleep(0.4)
            i += 1
    
    M_95() # start GIF animation
    threading.Thread(target=loadingAnimation).start()
    
    root.mainloop()
    

    Update: animate GIF more than one cycle in around 4 secs:

    def M_95(n=0, top=None, lbl=None):
        # Play GIF (file name = m95.gif) in a 320x320 tkinter window
        # Play GIF concurrently with the loading animation below
        # Close tkinter window after play
        global process_is_alive
        num_cycles = 2
        count = len(frames) * num_cycles
        delay = 4000 // count # make required cycles of animation in around 4 secs
        if n == 0:
            root.withdraw()
            top = tkinter.Toplevel()
            lbl = tkinter.Label(top, image=frames[0])
            lbl.pack()
            center_window(top)
            process_is_alive = True
            lbl.after(delay, M_95, n+1, top, lbl)
        elif n < count-1:
            lbl.config(image=frames[n%len(frames)])
            lbl.after(delay, M_95, n+1, top, lbl)
        else:
            top.destroy()
            root.destroy()
            process_is_alive = False