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
Python's in (_Contains_) Operator Returns a Bool Whose Value Is Neither True Nor False
Python Re.Sub Back Reference Not Back Referencing
How to Print the Key-Value Pairs of a Dictionary in Python
How to Get Stable Results with Tensorflow, Setting Random Seed
Validation of a Password - Python
Downloading a Directory Tree with Ftplib
How to Access a Standard-Library Module in Python When There Is a Local Module with the Same Name
How to Set Headers Using Python's Urllib
How Can a Recursive Regexp Be Implemented in Python
Re.Sub Erroring with "Expected String or Bytes-Like Object"
Factorize a Column of Strings in Pandas
In Python, How to Escape Newline Characters When Printing a String
How to Get All Combinations of Length N in Python
How to Get Current Function into a Variable