1

I am new to python and I came up with this idea on how to make a simple fade animation in python using tkinter and the time module. I have defined two animations for the program: one to fade in and the other one to fade out. The fade out animation works perfectly and exactly how I want it to be, however, the fade in animation does not work at all. The program pretty much doesn't show up until the while loop is finished. Am I doing something wrong or is it just impossible to create a fade in effect in python tkinter? Here is my code snippet:

from tkinter import *
import time

root = Tk()
transparency = 0
while transparency <= 1:
    transparency += 0.1
    root.wm_attributes("-alpha", transparency)
    time.sleep(0.03)


def fade():
    t = 1
    while t > 0:
        t -= 0.1
        root.wm_attributes("-alpha", t)
        time.sleep(0.03)
    root.destroy()


btn = Button(root, text='fade exit', command=fade).pack()
root.mainloop()
Omid Ketabollahi
  • 154
  • 2
  • 13

2 Answers2

2

Instead of using while loops and time, use after(millis, function) and recursion.

bonuses:

  • use the function arguments to customize the fade effect and behavior
  • this will not block the root from updating
  • everything is encapsulated
  • these functions can be used on any Toplevel window
  • applyFades manages everything in one call

If you absolutely need to attach these features to a Button, simply assign the command argument like this: command=lambda: fadeOut(root).

window.py

''' Fade In
    @window ~ the window to affect
    @millis ~ the amount of milliseconds to wait before next recursion
    @inc    ~ the amount to increment alpha on each recursion
'''
def fadeIn(window, millis:int=50, inc:float=0.1):
    alpha = float(window.attributes('-alpha')) + inc
    window.attributes('-alpha', alpha)
    if alpha < 1:
        window.after(millis, lambda: fadeIn(window, millis, inc))
    else:
        window.attributes('-alpha', 1.0)


''' Fade Out
    @window, @millis ~ see: Fade In
    @dec     ~ the amount to decrement alpha on each recursion
    @destroy ~ True|False destroy the window when effect is complete
'''
def fadeOut(window, millis:int=50, dec:float=0.1, destroy:bool=True):
    alpha = float(window.attributes('-alpha')) - dec
    window.attributes('-alpha', alpha)
    if alpha > 0:
        window.after(millis, lambda: fadeOut(window, millis, dec, destroy))
    else:
        window.attributes('-alpha', 0.0)
        if destroy:
            window.destroy()
            
            
''' Assign All Fades In One Call
    @window, @millis, @inc  ~ see: Fade In
    @dec, @destroy          ~ see: Fade Out
    @close ~ True|False add fadeOut effect to window close button
'''
def applyFades(window, millis:int=50, inc:float=0.1, dec:float=0.1, destroy:bool=True, close:bool=True):
    window.attributes('-alpha', 0.0)
    window.after(millis, lambda: fadeIn(window, millis, inc))
    if close:
        window.protocol("WM_DELETE_WINDOW", lambda: fadeOut(window, millis, dec, destroy))

        

main.py

import tkinter as tk
import window as win
        
  
root = tk.Tk()

win.applyFades(root) 

root.mainloop()
OneMadGypsy
  • 4,640
  • 3
  • 10
  • 26
  • Hi, @Michael! Hope you are having a great day! Thank you so much for the code snippet! It worked out for me! However, I really want to know why my code did not work. Was it solely because I used time.sleep or my entire algorithm is wrong? I would really like to know. Thanks! – Omid Ketabollahi Aug 18 '20 at 06:48
  • @Omid Ketabollahi Yes. The while loop was stopping mainloop from running til it was done. Essentially, you were blocking the gui from updating. Use `after()` when you need to `sleep`. – OneMadGypsy Aug 18 '20 at 11:23
2

Let's see what you script does. At the beginning root = Tk() is assigned which starts a tcl/tk interpreter and creates a root window. Then it controls its opacity attribute to fade in. After that a Button widget is placed on the root window which has these properties:

  • a text 'fade exit' is written on the top on the widget
  • it waits for a mouse click and after it controls opacity attribute of a root window to fade out.

Finally, root.mainloop() is a substitute for

while True:
    root.update_idletasks()
    root.update()

You might pay attention that 'fade in' button is being created at the moment your root window is not updated. This is a reason why you're not able to see actual fade in.

Solution 1. Updating root window after each sample of fading in:

from tkinter import *
import time

def fade():
    t = 1
    while t > 0:
        t -= 0.1
        root.wm_attributes("-alpha", t)
        time.sleep(0.03)
    root.destroy()

root = Tk()
transparency = 0
btn = Button(root, text='fade in', command=fade)
btn.pack()

while transparency <= 1:
    transparency += 0.1
    root.wm_attributes("-alpha", transparency)
    root.update_idletasks()
    root.update()
    time.sleep(0.03)

btn.configure(text='fade exit') #I guess no new button is needed and text should be replaced only
root.mainloop()

Solution 2. It's more typical not to use tkinter in combination with time and use after method. Check out Michael's answer.

mathfux
  • 5,759
  • 1
  • 14
  • 34