Play an animated GIF in python with tkinter while loading - python

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()

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:
use .after() to show the animated GIF because it is not safe to update tkinter widgets in a thread
use thread on loadingAnimation() instead
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

Related

pyautogui does not click based on slider/scale values in Tkinter

How I can make pyautogui click based on scale value in Tkinter ?
For example if the scale value is 10 the click delay (with pyautogui.PAUSE) should be of 0.1 it represent 10 clicks per second and if the scale value is 15 the delay should be of 0.15 => 15 clicks per second.
The problem clicks are not activated depending on the scale value
I also can't put root.mainloop() anywhere else than in the While True loop although I would have liked to put it at the very end of the program but in this case I would have to replace while True by "while "normal" == root.state()" (to stop the loop as soon as the Tkinter window closes) which doesn't work either
The code :
import mouse
import threading
import pyautogui
from tkinter import *
from tkinter import ttk
root = Tk()
root.geometry('500x400')
bovar = IntVar()
def func(*args):
if bovar.get() == 10:
pyautogui.PAUSE = 0.1
elif bovar.get() == 11:
pyautogui.PAUSE = 0.11
elif bovar.get() == 12:
pyautogui.PAUSE = 0.12
elif bovar.get() == 13:
pyautogui.PAUSE = 0.13
elif bovar.get() == 14:
pyautogui.PAUSE = 0.14
else:
pyautogui.PAUSE = 0.15
scale = Scale(root,orient=HORIZONTAL,from_=10,to=15,variable=bovar,command=func)
label = Label(root,text='CPS:')
label.pack(side=TOP)
scale.pack()
func()
root.mainloop()
def repeat_function(kill_event):
while not kill_event.is_set():
pyautogui.click()
while True:
kill_event = threading.Event()
new_thread = threading.Thread(target=lambda: repeat_function(kill_event))
mouse.on_button(new_thread.start, (), mouse.RIGHT, mouse.DOWN)
mouse.on_button(kill_event.set, (), mouse.RIGHT, mouse.UP)
mouse.wait(mouse.RIGHT, mouse.UP)
mouse.unhook_all()
root.mainloop() starts an infinite loop that waits for actions on the tkinter window and responds to them. Thus, any code you write under this call isn't going to execute until you close tkinter. If you want something to run simultaneously with this tkinter loop, it will have to run in another thread. Your logic of determining PAUSE is also a little flawed. A pause of 0.15 seconds doesn't mean it will click 15 times per second, but 1/0.15 times.
import mouse
import threading
import pyautogui
from tkinter import *
from tkinter import ttk
def convert_to_pause(clicks_per_second):
clicks_per_second = min(clicks_per_second, 15)
return 1 / clicks_per_second
def repeat_function(kill_event, int_var):
while not kill_event.is_set():
pyautogui.PAUSE = convert_to_pause(int_var.get())
pyautogui.click()
def hotkey_thread_loop(int_var):
# The loop you already have, we just need to run it in a separate thread
# We pass it the IntVar from the Scale so that it can dynamically read its value
while True:
kill_event = threading.Event()
# Here we also pass the IntVar to access its value in the new thread
new_thread = threading.Thread(target=lambda: repeat_function(kill_event, int_var))
mouse.on_button(new_thread.start, (), mouse.RIGHT, mouse.DOWN)
mouse.on_button(kill_event.set, (), mouse.RIGHT, mouse.UP)
mouse.wait(mouse.RIGHT, mouse.UP)
mouse.unhook_all()
# Start the main program
if __name__ == "__main__":
# Set up all tkinter stuff
root = Tk()
root.geometry('500x400')
bovar = IntVar()
scale = Scale(root,orient=HORIZONTAL,from_=10,to=15,variable=bovar,command=func)
label = Label(root,text='CPS:')
label.pack(side=TOP)
scale.pack()
# Create the thread to manage hotkey pressing and start it
# Set daemon=True to have it terminate when main program ends
hotkey_thread = threading.Thread(target=lambda: hotkey_thread_loop(bovar),
daemon=True)
hotkey_thread.start()
# Start the tkinter application, the ininite loop
root.mainloop()
# Any code put here (below mainloop()) will not execute while tkinter runs,
# don't try to implement more logic here

Fullscreen slideshow with images, text and video

I'm completely new at python, learned not much yet. My goal is, to build a slideshow app, which should display images, images with transparent text frames and also videos. Playing a video is my main problem at the moment.
A little test script with static content. Later, all content should be loaded from a web service.
import tkinter as tk
import tkvideo as tv
from PIL import Image
from PIL import ImageTk
import time
# fullscreen window
window = tk.Tk()
window.attributes('-fullscreen', True)
# dimensions
window_width = window.winfo_screenwidth()
window_height = window.winfo_screenheight()
text_width = int(window_width * 0.4)
text_x = int(window_width - text_width)
# transparent rectangle for text
imgs=[]
def create_rectangle(x,y,a,b,**options):
if 'alpha' in options:
# Calculate the alpha transparency for every color(RGB)
alpha = int(options.pop('alpha') * 255)
# Use the fill variable to fill the shape with transparent color
fill = options.pop('fill')
fill = window.winfo_rgb(fill) + (alpha,)
img = Image.new('RGBA', (a-x, b-y), fill)
imgs.append(ImageTk.PhotoImage(img, master=canvas))
canvas.create_image(x, y, image=imgs[-1], anchor='nw')
canvas.create_rectangle(x, y, a, b, **options)
# fullscreen canvas
canvas = tk.Canvas(window, bg="white", bd=0)
canvas.pack(fill=tk.BOTH, expand=True)
# only image slideshow
images = ['quiz1.jpg', 'quiz2.jpg', 'quiz3.jpg', 'quiz4.jpg']
for img in images:
image = Image.open("/home/jpm/Bilder/" + img)
newimage = image.resize((window_width, window_height))
photo = ImageTk.PhotoImage(newimage, master=canvas)
canvas.create_image(0, 0, anchor="nw", image=photo)
canvas.update()
time.sleep(5)
# image with text slideshow
images = ['1658724794aff.jpg', '1658724768kar.jpg']
headlines = ['Headline 1',
'Headline 2']
paragraphs = [['paragraph 1',
'paragraph 2',
'paragraph 3',
'paragraph 4'],
['paragraph 1',
'paragraph 2',
'paragraph 3',
'paragraph 4']]
i=0
for img in images:
image = Image.open("/home/jpm/Bilder/" + img)
newimage = image.resize((window_width, window_height))
photo = ImageTk.PhotoImage(newimage, master=canvas)
canvas.create_image(0, 0, anchor="nw", image=photo)
create_rectangle(text_x, 0, window_width, window_height, fill= "white", alpha=.80, width=0)
head = canvas.create_text(text_x+20, 20, text=headlines[i], fill="#72B765", font=('Helvetica 34 bold'), anchor='nw', width=text_width-20)
canvas.update()
x0, y0, x1, y1 = canvas.bbox(head)
for paragraph in paragraphs[i]:
time.sleep(4)
line = canvas.create_text(text_x+20, y1+10, text=paragraph, fill="#000000", font=('Helvetica 18 bold'), anchor='nw', width=text_width-20)
x0, y0, x1, y1 = canvas.bbox(line)
canvas.update()
canvas.update()
time.sleep(8)
i = i + 1
canvas.destroy()
my_label = tk.Label(window)
my_label.pack(fill='both', expand=True)
player = tv.tkvideo("/home/jpm/Bilder/docsite_promotion.mp4", my_label, loop = 0, size = (window_width,window_height))
player.play()
window.mainloop()
The image and image with text parts running fine. My problem is the video. First of all, it is only displayed at the end of the script. If I put the Label with the video between the two loops or in front of the first, nothing is shown. I also found out, that the program is not waiting till the video ends.
It's all too confusing for me, to explain it better. Maybe I don't understand the flow control of python programs at all. Maybe tkvideo is not the best option. I played also with ffpyplayer, but can't find out, how use it with tkinter.
I only want to display these three types of content in any order and finally also in an endless loop.
Hope you understand me and can give me some tips.
.play() doesn't wait for end of video (it runs code in separated thread) and next image may replace it (hide it) before you see video.
I tried to use sleep() to wait for end of video but it makes problem - it raises error because function doesn't run in main thread.
You would need to use .after(millisecond, my_function) to check periodically if there is end of video and later display images.
If in tkvideo you use self.thread instead of thread then you can check periodically self.thread.is_alive() to catch end of thread. But this need also some method to run next code which will change images. First idea is to create function which changes images and send it as callback to function which check end of video - and it would run this callback() when is_alive() is False. But all this makes problem when you would like to create something more complex.
Here example which uses window.after() to check self.thread.is_alive() and later it runs callback()
I uses tkvideo from my other answer - I added self.running to have method to stop video in any moment.
import time
import tkinter as tk
import threading
from time import perf_counter, sleep
import imageio
from PIL import Image, ImageTk
import os
class tkvideo():
def __init__(self, path, label, loop=False, size=(640,360), hz=0):
self.path = path
self.label = label
self.loop = loop
self.size = size
self.hz = hz
self.running = True # <-- variable to control loop
def load(self, path, label, loop, hz):
"""
Loads the video's frames recursively onto the selected label widget's image parameter.
Loop parameter controls whether the function will run in an infinite loop
or once.
"""
frame_data = imageio.get_reader(path)
if hz > 0:
frame_duration = float(1 / hz)
else:
frame_duration = float(0)
if loop:
while True:
before = perf_counter()
for image in frame_data.iter_data():
frame_image = ImageTk.PhotoImage(Image.fromarray(image).resize(self.size))
if not self.running: # <-- variable to control loop
return # exit function and stop thread
label.config(image=frame_image)
label.image = frame_image
diff = frame_duration + before
after = perf_counter()
diff = diff - after
if diff > 0:
sleep(diff)
before = perf_counter()
print('[load] end of loop')
else:
before = perf_counter()
for image in frame_data.iter_data():
frame_image = ImageTk.PhotoImage(Image.fromarray(image).resize(self.size))
if not self.running: # <-- variable to control loop
return # exit function and stop thread
label.config(image=frame_image)
label.image = frame_image
diff = frame_duration + before
after = perf_counter()
diff = diff - after
if diff > 0:
sleep(diff)
before = perf_counter()
print('[load] end of loop')
def play(self):
"""
Creates and starts a thread as a daemon that plays the video by rapidly going through
the video's frames.
"""
# uses `self.thread` instead of `thread` to have access to `self.thread.is_alive()`
self.thread = threading.Thread(target=self.load, args=(self.path, self.label, self.loop, self.hz))
self.thread.daemon = True
self.thread.start()
def show_video(filename, size, callback=None):
global player
global my_label
fullpath = os.path.join(folder, filename)
my_label = tk.Label(window)
my_label.pack(fill='both', expand=True)
player = tkvideo(fullpath, my_label, loop=False, size=size)
player.play()
# check after 5ms
window.after(5, check_end_video, callback)
def check_end_video(callback):
global my_label
if player.thread.is_alive(): # check if it end of thread
# check again after 5ms
window.after(5, check_end_video, callback)
else:
# remove label
print('The End')
my_label.destroy()
# run next function
if callback:
callback()
def show_image(filename, size, callback=None):
global my_label
fullpath = os.path.join(folder, filename)
frame_image = ImageTk.PhotoImage(Image.open(fullpath).resize(size))
my_label = tk.Label(window)
my_label.pack(fill='both', expand=True)
my_label['image'] = frame_image # show image on label
my_label.image = frame_image # solution for bug in PhotoImage
# check after 5000ms (5s)
window.after(5000, check_end_image, callback)
def check_end_image(callback):
global my_label
# remove label
print('The End')
my_label.destroy()
# run next function
if callback:
callback()
def other_function():
button = tk.Button(window, text='Close', command=window.destroy)
button.pack(fill='both', expand=True, padx=250, pady=250)
# --- main ---
folder = "/home/jpm/Bilder/"
window = tk.Tk()
window.update()
#window.attributes('-fullscreen', True)
#window_width = window.winfo_screenwidth()
#window_height = window.winfo_screenheight()
window.geometry('800x600+800+300')
window_width = 800
window_height = 600
# ---
size = (window_width, window_height)
# run video and it will later run `show_image`
show_video("docsite_promotion.mp4", size,
lambda:show_image('quiz1.jpg', size, other_function))
# ---
window.mainloop()

How can I switch Python Tkinter GUI camera source?

First of all, I wanted to put a camera switch option on a piece of code found here. First, I found the possible camera indexes and tried to change the camera sources with the push of a button. Even though the function worked, the camera source was not changed. I am looking for a solution for this.
In other words, how can I change the camera indexes through the function. The "returnCameraIndexes ()" function that I wrote before starting the 'main' section in this topic creates the total camera indexes. The "ChangeCam(" function is the function that performs the exchange between all the resources I have acquired.
import numpy as np
from multiprocessing import Process, Queue
from Queue import Empty
import cv2
from PIL import Image, ImageTk
import time
import Tkinter as tk
#tkinter GUI functions----------------------------------------------------------
def quit_(root, process):
process.terminate()
root.destroy()
def update_image(image_label, queue):
frame = queue.get()
im = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
a = Image.fromarray(im)
b = ImageTk.PhotoImage(image=a)
image_label.configure(image=b)
image_label._image_cache = b # avoid garbage collection
root.update()
def update_all(root, image_label, queue):
update_image(image_label, queue)
root.after(0, func=lambda: update_all(root, image_label, queue))
#multiprocessing image processing functions-------------------------------------
def image_capture(queue,change_camera_flag):
vidFile = cv2.VideoCapture(change_camera_flag)
while True:
try:
flag, frame=vidFile.read()
if flag==0:
break
queue.put(frame)
cv2.waitKey(20)
except:
continue
#check all camera Sources
def returnCameraIndexes():
# checks the first 10 indexes.
index = 0
arr = []
i = 10
while i > 0:
cap = cv2.VideoCapture(index)
if cap.read()[0]:
arr.append(index)
cap.release()
index += 1
i -= 1
return arr
camera_list = returnCameraIndexes()
change_camera_flag = 0
# Switch camera sourcess
def ChangeCam():
global change_camera_flag,cap_1
if(len(camera_list)-1 == change_camera_flag):
change_camera_flag = 0
else:
change_camera_flag = change_camera_flag + 1
print(change_camera_flag)
if __name__ == '__main__':
queue = Queue()
print ('queue initialized...')
root = tk.Tk()
print ('GUI initialized...')
image_label = tk.Label(master=root)# label for the video frame
image_label.pack()
print ('GUI image label initialized...')
p = Process(target=image_capture, args=(queue,change_camera_flag))
p.start()
print ('image capture process has started...')
change_source_button = tk.Button(master=root, text='ChangeCamera',command=lambda: ChangeCam())
change_source_button.pack()
# quit button
quit_button = tk.Button(master=root, text='Quit',command=lambda: quit_(root,p))
quit_button.pack()
print ('quit button initialized...')
# setup the update callback
root.after(0, func=lambda: update_all(root, image_label, queue))
print ('root.after was called...')
root.mainloop()
print ('mainloop exit')
p.join()
print ('image capture process exit')

Run Images in Loop Tkinter

I am creating an app that will allow users to scan a ticket and a message will be displayed. I have created a short GIF animation to play when the app starts to show users where to scan their ticket.
I am having trouble understanding how to play a GIF image using tkinter in Python 3. I have tried many solutions and I came across a piece of code where you select the folder and the images in that folder will play in a loop but it's not working.
I think I'm not understanding the code. Here is my code for my app:
from tkinter import *
from tkinter import messagebox
import tkinter.filedialog
from tkinter.filedialog import askdirectory
import requests
import simplejson as json
import os
#from json import JSONEncoder
#class MyEncoder(JSONEncoder):
#def default(self, o):
#return o.__dict__
#Connect to API function
def apiconnect(statusvar):
ticektid = e1.get()
def to_serializable(ticketid):
return str(ticketid)
url = "https://staging3.activitar.com/ticket_api/tickets"
data = {'ticket_id':e1.get(),'direction': 'up'}
headers = {'Content-Type': 'application/json','Authorization' :'J0XDvDqVRy9hMF9Fo7j5'}
r = requests.post(url,data=json.dumps(data), headers=headers)
requestpost = requests.post(url, headers=headers, json=data)
response_data = requestpost.json()
statusvar = (response_data["status"])
messagevar = (response_data["message"])
json.dumps(url,data)
# MyEncoder().encode(ticketid)
#'{"ticekt_id": "/foo/bar"}'
#19 February 2018
#def from_json(json_object):
# if 'ticket_id' in json_object:
# return FileItem(json_object['ticket_id'])
# ticketid = JSONDecoder(object_hook = from_json).decode('{"ticket_id": "/foo/bar"}')
#Including GPIO config
if statusvar == "failed":
messagebox.showinfo("Cape Point", messagevar)
else: statusvar == "successful"
#Run at full screen automatically:
#---------------Function & Class--------------------------------#
class FullScreenApp(object):
def __init__(self, master, **kwargs):
self.master=master
pad=3
self._geom='200x200+0+0'
master.geometry("{0}x{1}+0+0".format(
master.winfo_screenwidth()-pad, master.winfo_screenheight()-pad))
master.bind('<Escape>',self.toggle_geom)
def toggle_geom(self,event):
geom=self.master.winfo_geometry()
print(geom,self._geom)
self.master.geometry(self._geom)
self._geom=geom
#--------------------------------------------------------------------#
def next_img():
img_label.img = PhotoImage(file=next(imgs))
img_label.config(image=img_label.img)
#create a textbox on a form
root = Tk()
#-----Full Screen-------#
app = FullScreenApp(root)
root.title("Cape Point")
root.configure(background = 'White')
#________ this code below was the original that displayed a static image _____#
#titlepic = PhotoImage(file = "ScanPlease.gif")
#shownpic = titlepic
#filename = shownpic
#Label(root, image = filename).grid(row=0, sticky=W)
img_dir = askdirectory(parent=root, initialdir= "C:/Users/Nickitaes/Desktop", title='Where To Scan')
os.chdir(img_dir)
imgs = iter(os.listdir(img_dir))
img_label = Label(root)
img_label.bind("<Return>",next_img())
next_img()
e1 = Entry(root)
e1.grid(row=1, column=0)
e1.focus_set() #set cursor focus to textbox
e1.bind("<Return>", apiconnect) #Return function
root.mainloop( )
Thanks for the help!
Well..., it was not hard to find other questions about this on StackOverflow. Here are some: Play Animations in GIF with Tkinter and Play an Animated GIF in python with tkinter.
I have combined the answers to take care of different number of subpictures an also commented the code a bit more.
from tkinter import *
import time
root = Tk()
framelist = [] # List to hold all the frames
for ix in range(1000): # range > frames in largest GIF
part = 'gif -index {}'.format(ix)
try: frame = PhotoImage(file='giphy.gif', format=part)
except:
last = len(framelist) - 1 # Save index for last frame
break # Will break when GIF index is reached
framelist.append(frame)
def update(ix):
if ix > last: ix = 0 # Reset frame counter if too big
label.configure(image=framelist[ix]) # Display frame on label
ix += 1 # Increase framecounter
root.after(100, update, ix) # Run again after 100 ms.
label = Label(root)
label.pack()
root.after(0, update, 0) # Start update(0) after 0 ms.
root.mainloop()
Adjust the for-loop for the GIF size you use, or rewrite as a while-loop.
I don't know how to read the frame delay from the GIF. You'll have to try different values in after() until it looks good.

generating animation in tkinter

I wrote a small program using tkinter to generate a figure based on parameters I'm giving the program (through gui interface). I am drawing the figure directly with tkinter using Canvas. Now I want to generate a series of figures and put them in animated loop. I can generate a gif animation and load it into the figure but the question is if I can do this directly in tkinter. I can put everything in loop, but then I'll lose the event handler. Any other option?
Edit
Here is a simple script:
#!/usr/bin/python
from Tkinter import *
import ttk
import numpy as np
colors = {1:'#F00',
2:'#0F0',
3:'#00F',
4:'#FF0',
5:'#0FF'}
root = Tk()
canvas = Canvas(root,width=400,height=400)
label = Label(root,text='seed')
values = StringVar()
combo = ttk.Combobox(root)
def painter(event):
seed = int(combo.get())
np.random.seed(seed)
nrows = 10
ncols = 10
Y = 0
dx = 400/nrows
dy = 400/ncols
for i in range(nrows):
X = 0
for j in range(ncols):
n = np.random.randint(1,5)
col = colors[n]
canvas.create_rectangle(X,Y,X+dx,Y+dy,width=1,fill=col)
X += dx
Y += dy
canvas.grid(column=0,row=0,rowspan=2)
combo.grid(row=1,column=1)
label.grid(row=0,column=1)
combo['values'] = [1,2,3,4]
combo.bind('<<ComboboxSelected>>',painter)
combo.current(0)
painter(None)
root.mainloop()
I could change the painter function to include an infinite loop but this way I never reach the mainloop(), so I need to call this only after the main loop is created, but how?
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.

Categories