Search code examples
pythonanimationcanvas

Python animation beginner errors with AFTER and Canvas display


My code just draws a set of horizontal lines within a circle in a window. I wrote it so that there would be a pause between the drawing of the lines. I used the "AFTER" function for this. However, it executes without any delays and only shows the finished screen (all iterations completed). It stops when the maximum number of iterations is exceeded. I have not found a way to stop the code by counting numbers of loops etc. I have read extensively through forums but have not found an explanation that I can follow.

I must be missing something very basic hence this plea for help.

I have created a window, canvas, and drawn on it all successfully. I can call the function that draws the line but cannot get the AFTER function to delay this call. If I iterate the call by having the function call itself (I understand this is the recommended method of making an animation) then the Window with the canvas does not open at all until the programme terminates (through exceeding the number of allowed iterations). If I print anything during execution (e.g. to track the operation of the code) then again the window with the canvas does not open.

The code is here:

from tkinter import *

# **setup canvas**
window = Tk() 
window.title("solid show")
window.resizable(False, False)
window.tk_setPalette(background='#000000')

canvas_width = 600
canvas_height = 600
mycanvas=Canvas(window,width=canvas_width,height=canvas_height)

# **FUNCTION TO DRAW LINES**    
def drawlines(movestep):
    mycanvas.create_line(150, movestep, 300, movestep,fill="#000000")
    mycanvas.pack()
    movestep = movestep + 10
    #if movestep < 450 : - NOT USED, FAILED ATTEMOT AT TRAPPING AND HALTING EXECUTION
    window.after(10000,drawlines(movestep))

movestep = 150 # PARAMETER TO SEPARATE DRAWN LINES
mycanvas.create_oval(45, 45, 555, 555, fill='#cccccc') # CIRCLE TO DRAW LINES IN
mycanvas.pack()
window.after(10000,drawlines(movestep))

window.mainloop()

Solution

  • window.after is expecting a time delay and a function. The problem is that when you call window.after(10000, drawlines(movestep)), drawlines(movestep) isn't a function - it's a function call.

    To explain it more concretely, here's an example. Suppose we want to use window.after to delay the addition of 2 and 3 by one second. You might be tempted to try something like this:

    from tkinter import Tk
    
    def add(a, b):
        return a+b
    
    window = Tk()
    window.after(1000, add(2, 3))
    

    But this isn't going to call testFunction(2, 3) in one second - it's going to call it now and then try to call 5 in one second. It turns out that this doesn't throw an error, but it also doesn't do what you want it to.

    This is the same error you're facing. When you call window.after(10000, drawlines(movestep)), the interpreter will try to call drawlines(movestep) immediately, then call its return value in ten seconds. (In practice, this causes a recursion depth error, but that's irrelevant.)

    To fix this, you can use partial functions. These let you pass arguments into a function, then pass that function as an argument - all without calling the function immediately. For our addition example, that would look something like this. Here's the completed code:

    from tkinter import Tk
    from functools import partial
    
    def add(a, b):
        return a+b
    
    window = Tk()
    add_2_and_3 = partial(add, 2, 3)
    window.after(1000, add_2_and_3)
    

    In your case, you only need to change the two lines where you call window.after and add an import statement to the top:

    from tkinter import *
    from functools import partial
    
    # **setup canvas**
    window = Tk() 
    window.title("solid show")
    window.resizable(False, False)
    window.tk_setPalette(background='#000000')
    
    canvas_width = 600
    canvas_height = 600
    mycanvas=Canvas(window,width=canvas_width,height=canvas_height)
    
    # **FUNCTION TO DRAW LINES**    
    def drawlines(movestep):
        mycanvas.create_line(150, movestep, 300, movestep,fill="#000000")
        mycanvas.pack()
        movestep = movestep + 10
        if movestep < 450: # This works now!
            window.after(1000, partial(drawlines, movestep))
    
    movestep = 150 # PARAMETER TO SEPARATE DRAWN LINES
    mycanvas.create_oval(45, 45, 555, 555, fill='#cccccc') # CIRCLE TO DRAW LINES IN
    mycanvas.pack()
    window.after(1000, partial(drawlines, movestep))
    
    window.mainloop()
    

    If your version of Python doesn't support partials in after, you can also replace partial(drawlines, movestep) with the lambda function lambda: drawlines(movestep).

    (PS: I shortened your delays from 10000 ms to 1000 ms so the animation doesn't take forever.)