Simple Animation Using Tkinter

simple animation using tkinter

The basic pattern for doing animation or periodic tasks with Tkinter is to write a function that draws a single frame or performs a single task. Then, use something like this to call it at regular intervals:

def animate(self):
self.draw_one_frame()
self.after(100, self.animate)

Once you call this function once, it will continue to draw frames at a rate of ten per second -- once every 100 milliseconds. You can modify the code to check for a flag if you want to be able to stop the animation once it has started. For example:

def animate(self):
if not self.should_stop:
self.draw_one_frame()
self.after(100, self.animate)

You would then have a button that, when clicked, sets self.should_stop to False

generating animation in tkinter

a simple example of some animation using tkinter canvas:

import tkinter as tk

class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title("Canvas animation example")

self.c = tk.Canvas(self, width=400, height=400)
self.c.pack()

self.f_index = 0 # index so we know which frame to draw next
# array to hold our frame data,
# you'll probably need this to hold more than
# just a set of coordinates to draw a line...
self.f_data = []

for num in range(0, 400, 5): # make up a set of fake data
self.f_data.append([num, num, num+10, num+10])

def next_frame(self):
data = self.f_data[self.f_index] # fetch frame data
self.c.delete('all') # clear canvas
self.c.create_line(*data) # draw new frame data
self.f_index += 1 # increment frame index
if (self.f_index >= len(self.f_data)): # check and wrap if at end of sequence
self.f_index = 0
self.c.after(50, self.next_frame) # call again after 50ms

if __name__ == "__main__":
app = App()
app.next_frame() # called manually once to start animation
# could be started with 'after' instead if desired
app.mainloop()

note that the more items you want to delete and redraw the slower this code will perform. you need to have realistic expectations, drawing a few 10's of items will be fine, a few 100's might be ok on a decent pc, but 1000's of items would be very poorly performing.

Making a loading animation when pulling data from the web in Tkinter

The reason why the program didn't work was because the functions were running in order. It worked when I ran the functions simultaneously using 'threading'.

from tkinter import*
from tkinter import ttk
import io
from PIL import Image, ImageTk
import requests,threading
from urllib.request import urlopen

class Root(Tk):
def __init__(self):
super().__init__()

self.canvas =Canvas(
width = 720, height = 1000)
self.canvas.place(relx=0,rely=0)

self.button = Button(self, text="Get The Posters",
command=self.play)
self.button.pack()

self.movie_list=["Tenet","Memento","Inception"]
self.poster_list=[]
self.x=0
self.label=Label(text="loading...please wait...")
self.pb = ttk.Progressbar(orient='horizontal',mode='indeterminate',length=480)

def animation(self):
self.button.destroy()
self.label.place(relx=0.5,rely=0.85,anchor='c')
self.pb.place(relx=0.5,rely=0.9,anchor='c')
self.pb.start()

def play(self):
self.t1 = threading.Thread(target=self.animation)
self.t2 = threading.Thread(target=self.get_posters)
self.t1.start()
self.t2.start()

def get_posters(self):

for i in self.movie_list:
self.url = "http://www.omdbapi.com/?apikey=73a4d84d&t={}".format(i)
self.response = requests.get(self.url)
self.data = self.response.json()
self.pic_url=self.data["Poster"]
self.my_page = urlopen(self.pic_url)
self.my_picture = io.BytesIO(self.my_page.read())
self.pil_img = Image.open(self.my_picture).resize((200,296))
self.tk_img = ImageTk.PhotoImage(self.pil_img)
self.poster_list.append(self.tk_img)

for n in range(len(self.poster_list)):
self.x+=30
if n==1:
self.x+=200
if n==2:
self.x+=200
self.canvas.create_image(
self.x,100,image=self.poster_list[n],anchor=NW)

self.label.destroy()
self.pb.destroy()

root = Root()
root.mainloop()

Using tkinter after to produce an animation

That after(5000, …) means 5 seconds after right now, as after is being called, not 5 seconds after some future point in time that tkinter can only guess by reading your mind.

So, you're just creating 200 callbacks, and scheduling them all to run 5 seconds from now. That's obviously not what you want, but it's what you're asking for, so that's what you get.


In general, you can't do loops like this in event-based programming. What you need to do is turn the loop inside-out: each step does one iteration, then schedules the next call for the next one.


The fully-general transformation looks like this:

def runAnim():
iterwidth = iter(range(0, 200))
stepAnim(iterwidth)

def stepAnim(iterwidth):
try:
width = next(iterwidth)
except StopIteration:
return
test_label.config(width=width))
app.after(5000, stepAnim, iterwidth)

While that works for any iterable, when you're just iterating over numbers, it's usually a bit nicer to turn the for loop into an explicit counter, which is easier to invert. (Yes, that's the opposite of the "usual for instead of while and += 1 when you're not inverting things. The difference is that here, we can't access the magic of for or while, and while is a lot less magical, and therefore easier to invert.)

def runAnim():
stepAnim(0, 200):

def stepAnim(width, maxWidth):
test_label.config(width=width))
width += 1
if width < maxWidth:
app.after(5000, stepAnim, width, maxWidth)

However, in this particularly simple case, you might be able to get away with scheduling 200 callbacks, ranging from 5 to 1000 seconds into the future:

def runAnim():
for width in range(0, 200):
app.after(5000 * width, lambda width = width: test_label.config(width=width))

This might cause the timer to drift a lot more badly, or it might even choke up the scheduler and add lag to your program, but it's at least worth trying.


Speaking of drift:

Back at the start, I mentioned that after(5000, …) means 5 seconds after right now.

An after can fire a bit late. As the docs say: "Tkinter only guarantees that the callback will not be called earlier than that; if the system is busy, the actual delay may be much longer."

So, what happens if it fires after, say, 5.2 seconds? Then the second tick happens 5 seconds after that, at 10.2 seconds, not at 10 seconds. And if they're all firing a bit late, that adds up, so by the end, we could be 20 seconds behind.

Worse, what if after fires exactly at 5.0 seconds, but the Label.config takes 0.2 seconds to run? Then we're absolutely guaranteed to be 20 seconds behind. (Plus any additional error from after itself.)

If this matters, you need to keep track of the desired "next time", and wait until then, not until 5 seconds from whenever it is now. For example:

import datetime as dt

def runAnim():
stepAnim(0, 200, dt.datetime.now() + dt.timedelta(seconds=5):

def stepAnim(width, maxWidth, nextTick):
test_label.config(width=width))
width += 1
if width < maxWidth:
now = dt.datetime.now()
delay = (nextTick - now).total_seconds() * 1000
nextTick += dt.timedelta(seconds=5)
app.after(delay, stepAnim, width, maxWidth, nextTick)

Why is my simple tkinter animation lagging?

Dont use threading if there is no process that would requier it. The after method works just fine.

import tkinter
from random import randint

canvas = tkinter.Canvas(width=1000,height=500)
canvas.pack()

class Witch:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
self.tag = "witch-%d" % id(self)
self.tags = ("witch", self.tag)
self.draw_witch(self.x, self.y)

def draw_witch(self, x, y):
canvas.create_oval(x,y-20,x+20,y, tags=self.tags)
canvas.create_rectangle(x,y,x+20,y+40, tags=self.tags)
canvas.create_line(x+40,y+20,x-20,y+20, tags=self.tags)
for i in range(-4,4):
canvas.create_line(x-40,y+20+i*5,x-20,y+20, tags=self.tags)

def move(self, speed):
canvas.move(self.tag, speed, speed)

def landing(x, y):
entity = Witch(x, y)
speed = randint(1,10)
animate(entity,speed)
def animate(entity,speed):
if canvas.coords(entity.tag)[2]+40 < 1000 and canvas.coords(entity.tag)[3]+40 < 500:
entity.move(speed)
canvas.after(10,animate,entity,speed)

landing(randint(50, 400), randint(50, 200))
landing(randint(50, 400), randint(50, 200))

canvas.mainloop()

Play Animations in GIF with Tkinter

You have to drive the animation yourself in Tk. An animated gif consists of a number of frames in a single file. Tk loads the first frame but you can specify different frames by passing an index parameter when creating the image. For example:

frame2 = PhotoImage(file=imagefilename, format="gif -index 2")

If you load up all the frames into separate PhotoImages and then use timer events to switch the frame being shown (label.configure(image=nextframe)). The delay on the timer lets you control the animation speed. There is nothing provided to give you the number of frames in the image other than it failing to create a frame once you exceed the frame count.

See the photo Tk manual page for the official word.



Related Topics



Leave a reply



Submit