I'm attempting to add a .jpeg image to my GUI while it has several other widgets.
With my code, I can display the image in a separate Tkinter window when I use the following command:
#self.label = Label(image = self.img)
However, whenever I try to add the image to the original Tkinter window, I get the error seen below my code. The way I tried to add it to the original Tkinter window is:
#self.label = Label(frame, image = self.img)
Replicating the error
Oddly enough, when I try to replicate the error in a shorter version of the code (such as directly below), it works. HOWEVER! To replicate the error in the shortened code, you need to create a different error first. Example: Replace text = "Try" with text = "%s" %yikes (because there is no variable yikes it will give you an error). After you change the code back to the EXACT way it was before, it produces the error I've descried below (TclError: image "pyimage__" doesn't exit). At the very bottom, I've included the entire class since I'm having difficulty consistently replicating the issue. I'm using Python 2.7 and Canopy 1.5.5.
Shortened code:
import matplotlib.pyplot as plt
from Tkinter import *
from PIL import ImageTk, Image
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import numpy as np
from tkFileDialog import askopenfilename, askdirectory
class App:
def __init__(self, master):
frame = Frame(master)
self.button_left = Button(frame,text="< Previous Event")
self.button_left.grid(row=1,column=0)
self.button_right = Button(frame,text="Next Event >")
self.button_right.grid(row=1,column=3)
#Creating text for the UI indicating the number of leakage events
w = Label(frame, text="Trying to Recreate error")
w.grid(row=1,column=2)
self.m = Canvas(frame,width=50,height=25)
self.text_id = self.m.create_text(25,12.5, text="Try")
self.m.grid(row=1,column=1)
self.path = "C:\Carbonite\EL_36604.02_231694\EL_36604.02_231694_2015-06-15 10.39.57.jpeg"
self.image = Image.open(self.path)
self.img = ImageTk.PhotoImage(self.image)
#self.label = Label(image = self.img)
self.label = Label(frame,image = self.img)
self.label.image = self.img
self.label.grid(row = 3, column = 0)
frame.grid(row=0,column=0)
root = Tk()
app = App(root)
root.mainloop()
Error I receive in my program when I use the commented out method:
TclError Traceback (most recent call last)
C:\Carbonite\Main_interface_file.py in <module>()
136
137 root = Tk()
--> 138 app = App(root)
139 root.mainloop()
140
C:\Carbonite\Main_interface_file.py in __init__(self, master)
72 self.img = ImageTk.PhotoImage(self.image)
73 #self.label = Label(image = self.img)
---> 74 self.label = Label(frame,image = self.img)
75 self.label.image = self.img
76 self.label.grid(row=3, column = 0)
C:\Users\U10596\AppData\Local\Enthought\Canopy\App\appdata\canopy-1.5.5.3123.win-x86_64\lib\lib-tk\Tkinter.pyc in __init__(self, master, cnf, **kw)
2585
2586 """
-> 2587 Widget.__init__(self, master, 'label', cnf, kw)
2588
2589 class Listbox(Widget, XView, YView):
C:\Users\U10596\AppData\Local\Enthought\Canopy\App\appdata\canopy-1.5.5.3123.win-x86_64\lib\lib-tk\Tkinter.pyc in __init__(self, master, widgetName, cnf, kw, extra)
2084 del cnf[k]
2085 self.tk.call(
-> 2086 (widgetName, self._w) + extra + self._options(cnf))
2087 for k, v in classes:
2088 k.configure(self, v)
TclError: image "pyimage8" doesn't exist
Almost entire Code:
import matplotlib.pyplot as plt
from Tkinter import *
from PIL import ImageTk, Image
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import numpy as np
from images_to_list import images_to_list
from tkFileDialog import askopenfilename, askdirectory
#Creating a class that creates the UI
class App:
def __init__(self, master):
self.event_num = 1
# Create a container
frame = Frame(master)
# Create 2 buttons (changes between leakage events
self.button_left = Button(frame,text="< Previous Event",
command=self.decrease)
self.button_left.grid(row=1,column=0)
self.button_right = Button(frame,text="Next Event >",
command=self.increase)
self.button_right.grid(row=1,column=3)
#Creating text for the UI indicating the number of leakage events
w = Label(frame, text="/ %s " % len(tft))
w.grid(row=1,column=2)
#Display the number of the current event in the series
self.m = Canvas(frame,width=50,height=25)
self.text_id = self.m.create_text(25,12.5, text="%s" % (self.event_num+1))
self.m.grid(row=1,column=1)
#Creating the plot of voltage data
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
self.fig.autofmt_xdate()
import matplotlib.dates as mdates
self.ax.fmt_xdata = mdates.DateFormatter('%Y-%m-%d')
self.line, = self.ax.plot(tft[self.event_num],tf1[self.event_num],'.')
self.line2, = self.ax.plot(tft[self.event_num],tf2[self.event_num],'.')
self.ax.set_ylim([0,3.5])
self.path = "C:\Carbonite\EL_36604.02_231694\EL_36604.02_231694_2015-06-15 10.39.57.jpeg"
self.image = Image.open(self.path)
self.img = ImageTk.PhotoImage(self.image)
#self.label = Label(image = self.img)
self.label = Label(frame,image = self.img)
self.label.image = self.img
self.label.grid(row=3, column = 0)
self.canvas = FigureCanvasTkAgg(self.fig,master=master)
self.canvas.show()
self.canvas.get_tk_widget().grid(row=1,column=0)
frame.grid(row=0,column=0)
#Creating a textbox to jump to event number
self.textbox = Entry(frame,width=5)
button1 = Button(frame, text='Go', command=self.letsgo) #Linking "Go" button with letsgo function to jump to event number
self.textbox.grid(row=2,column=1)
button1.grid(row=2,column=2)
#function letsgo allows the user to jump to any event in the series
def letsgo(self):
txt = self.textbox.get()
try:
self.event_num = int(txt)
except ValueError:
print "Opps! The number you enter needs to be an integer!"
self.line.set_xdata(tft[self.event_num])
self.line.set_ydata(tf1[self.event_num])
self.line2.set_xdata(tft[self.event_num])
self.line2.set_ydata(tf2[self.event_num])
self.ax.set_xlim([min(tft[self.event_num]),max(tft[self.event_num])])
self.canvas.draw()
self.m.itemconfig(self.text_id, text="%s" % (self.event_num+1))
#function decrease allows the user to use the decrease button
def decrease(self):
if self.event_num == 0: #if statement accounts for if the user tries to see the event previous to the first one
self.event_num = len(tft)-1
else:
self.event_num -= 1
self.line.set_xdata(tft[self.event_num])
self.line.set_ydata(tf1[self.event_num])
self.line2.set_xdata(tft[self.event_num])
self.line2.set_ydata(tf2[self.event_num])
self.ax.set_xlim([min(tft[self.event_num]),max(tft[self.event_num])])
self.canvas.draw()
self.m.itemconfig(self.text_id, text="%s" % (self.event_num+1))
#function increase allows the user to use the increase button
def increase(self):
if self.event_num == len(tft)-1: #if statement accounts for if the user tries to see the event after the last one.
self.event_num = 0
else:
self.event_num += 1
self.line.set_xdata(tft[self.event_num])
self.line.set_ydata(tf1[self.event_num])
self.line2.set_xdata(tft[self.event_num])
self.line2.set_ydata(tf2[self.event_num])
self.ax.set_xlim([min(tft[self.event_num]),max(tft[self.event_num])])
self.canvas.draw()
self.m.itemconfig(self.text_id, text="%s" % (self.event_num+1))
root = Tk()
app = App(root)
root.mainloop()
You need to grid the Frame. When I add
frame.grid()
at the bottom of your __init__ method it works fine. I don't get an error when I run the code you posted, though. It just doesn't display the label. I wonder if this is some platform-dependent behavior. I'm using python 2.7 on Yosemite 10.10.5.
EDIT: I've tried your extended example, and as expected, I don't see the behavior you. I can change the code back and forth. When it's incorrect, I get an error; when it's correct, it runs fine.
The behavior you describe must have something to do with Canopy. The way the python interpreter works, it will compile the script to byte codes every time it runs; it doesn't know anything about what the script used to says.
Can you try running the suspect script from the command line? Also, perhaps you should add the canopy tag to your question.
I don't use Canopy, so I don't think I can help you, but I'm leaving the answer here as background for someone who hopefully can.
Good luck.
I found the issue!!
I used askopenfilename() in the very beginning of my code, which opened an extra Tkinter window. As a result, there were two Tkinter windows open and confused the program.
By sending everything to the first Tk window that was created (and removing the second), it resolved the issue.
Related
I have made a GUI in which on user values a line is drawn, and when you click on the line another window opens where you can select something. And when you press ok it should print on the terminal. I think because i am creating a new tkinter window when i click on the line, i am unable to retrieve the user selection.How can i solve this problem? Your help is greatly appreciated.
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib import style
import tkinter as tk
from tkinter import ttk
from tkinter import *
import random
import numpy as np
LARGE_FONT = ('Verdana',12)
style.use('ggplot')
from matplotlib import pyplot as plt
class PageOne(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
label = tk.Label(self,text='Experiment', font = LARGE_FONT)
label.pack(pady=10,padx=10)
self.adding_widgets()
button2= ttk.Button(self,text='Validate', command=self.draw)
button2.pack()
button3= ttk.Button(self,text='Erase', command=self.dlet)
button3.pack()
self.lines_pts=[]
self.f1= Figure(figsize=(5,5),dpi=100)
self.b=self.f1.add_subplot(1,1,1)
self.b.set_xlim([0,10])
self.b.set_ylim([0,10])
self.canvas = FigureCanvasTkAgg(self.f1, self)
self.canvas.draw()
self.canvas.get_tk_widget().pack(side=tk.TOP,fill=tk.BOTH,expand=True)
self.f1.canvas.mpl_connect('pick_event', self.on_pick)
self.lines =[]
def adding_widgets(self,*args):
labfram=ttk.LabelFrame(self,width=100,height=100,text='Enter Member Coordinates',labelanchor=N)
labfram.pack()
l1=ttk.Label(labfram,text='x0')
l1.pack(side='left')
self.x0=Entry(labfram,width=10,)
self.x0.pack(side='left')
l2=ttk.Label(labfram,text='y0')
l2.pack(side='left')
self.y0=Entry(labfram,width=10)
self.y0.pack(side='left')
l3=ttk.Label(labfram,text='x1')
l3.pack(side='left')
self.x1=Entry(labfram,width=10)
self.x1.pack(side='left')
l4=ttk.Label(labfram,text='y1')
l4.pack(side='left')
self.y1=Entry(labfram,width=10)
self.y1.pack(side='left')
def draw(self):
p0 = float(self.x0.get()), float(self.y0.get())
p1 = float(self.x1.get()), float(self.y1.get())
self.lines_pts.append((p0,p1))
for p0,p1 in self.lines_pts:
x0, y0, x1, y1 = *p0, *p1
X = x0,x1
Y = y0,y1
ax = self.b.plot(X,Y, 'r', linewidth=4,picker=5 )
self.lines.append(ax)
self.canvas.draw()
def dlet(self):
self.b.lines.remove(self.b.lines[-1])
self.canvas.draw()
def on_pick(self,event):
w=Tk()
w.title('Channel Select')
w.geometry('250x50')
n = StringVar()
ch = ttk.Combobox(w, width = 15 , textvariable = n)
ch['values'] = ('CH1','CH2','CH3','CH4','CH5','CH6','CH7','CH8','CH9','CH10')
ch.grid(column=1,row=0)
ch_label = ttk.Label(w,text='Select Your Channel')
ch_label.grid(column=0,row=0)
ch_button = ttk.Button(w,text='OK',command=lambda: print ('value is:'+ n.get()))
ch_button.grid(column=1,row=1)
for line in self.lines:
line = event.artist
xdata, ydata = line.get_data()
ind = event.ind
print('on pick line:', np.array([xdata[ind], ydata[ind]]).T)
app = PageOne()
app.mainloop()
It's here do you really want to create a new window?
def on_pick(self,event):
w=Tk()
w.title('Channel Select')
w.geometry('250x50')
Take note that when you do this w=Tk() you are creating an instance of an object a NEW OBJECT which is Tk() so that's why it creates a new window. What is your goal are you really trying to pop up a window whenever you click cause if not you I think you can remove this.
----UPDATE---
So the reason why your window keeps popping out is because on your function you create an instance of an object with it. Then I tried putting the w=Tk() away from the function but still it would create or show that object cause it is within the mainloop.
Alternative solution is you can check if once that window exists or its state is normal then you can just do a focus.
Here is the code that I've added only on your on_pick function. I also added on your __init__ method a self.w = None to just set the w variable to None initially.
OVERALL this are only the changes made
def on_pick(self,event):
try:
if self.w.state() == "normal":
self.w.focus()
except BaseException as on_pick_error:
self.w=Toplevel()
self.w.title('Channel Select')
self.w.geometry('250x50')
n = StringVar()
ch = ttk.Combobox(self.w, width = 15 , textvariable = n)
ch['values'] = ('CH1','CH2','CH3','CH4','CH5','CH6','CH7','CH8','CH9','CH10')
ch.grid(column=1,row=0)
ch_label = ttk.Label(self.w,text='Select Your Channel')
ch_label.grid(column=0,row=0)
ch_button = ttk.Button(self.w,text='OK',command=lambda: print ('value is:'+ n.get()))
ch_button.grid(column=1,row=1)
for line in self.lines:
line = event.artist
xdata, ydata = line.get_data()
ind = event.ind
print('on pick line:', np.array([xdata[ind], ydata[ind]]).T)
You might wonder what this does, it just checks if the state of your self.w which happens to be your window that pops out, it just checks if its state is equal to "normal" meaning that it is active, then when it is it will just do a .focus() and focus on that current window. Else it would create a new window which is this self.w=Toplevel() and so on.
try:
if self.w.state() == "normal":
self.w.focus()
except BaseException as on_pick_error:
self.w=Toplevel()
self.w.title('Channel Select')
self.w.geometry('250x50')
Why is it Toplevel instead of Tk?
I would suggest to have it Toplevel however it is up to you to decide since Toplevel and Tk I think might just have the same properties but do note they are not the same so it's up to you.
I have several widgets, or more correctly, Labels, that update perfectly when a method finishing with self.after() is called in __init__.
That's not the case with this Label here, which gets the weather Information from OWM (OpenWeatherMap) through pyowm, and should be updated every specific amount of time, defined in the after() function.
I've tried almost everything in my knowledge, though I'm pretty newbie to Python. I've searched google for days, but no working solution was found, or not that I could make it work.
I've tried to put the after function in every method, even __init__.
The trimmed down main() for debug and the Weather Class follows:
import tkinter as tk
from Weather import Meteo
def displayMeteo(win):
# Place Meteo portlet
meteoHandler = Meteo(win, bg='black', fg='white')
meteoHandler.pack(side='top', anchor='ne')
def main():
# Set the screen definition, root window initialization
root = tk.Tk()
root.configure(background='black')
width, height = root.winfo_screenwidth(),
root.winfo_screenheight()
root.geometry("%dx%d+0+0" % (width, height))
label = tk.Label(root, text="Monitor Dashboard", bg='black',
fg='red')
label.pack(side='bottom', fill='x', anchor='se')
# Display portlet
displayMeteo(root)
# Loop the GUI manager
root.mainloop(0)
###############################
# MAIN SCRIPT BODY PART #
###############################
if __name__ == "__main__":
main()
And the class:
import pyowm
import tkinter as tk
from PIL import Image, ImageTk
class Meteo(tk.Label):
def __init__(self, parent=None, **params):
tk.Label.__init__(self, parent)
self.API = pyowm.OWM('API KEY', config_module=None,
language='it', subscription_type=None)
self.location = self.API.weather_at_place('Rome,IT')
self.weatherdata = self.location.get_weather()
self.weather = str(self.weatherdata.get_detailed_status())
self.dictionary = {'poche nuvole': 'Parzialmente Nuvoloso',
'cielo sereno': 'Sereno', 'nubi sparse': 'Nuvoloso',
'temporale con pioggia': 'Temporale', 'pioggia leggera':
'Pioggerella'}
self.Display()
def Temperatur(self):
self.Temp = tk.StringVar()
self.tempvalue = self.weatherdata.get_temperature('celsius')
self.temperature = str(self.tempvalue.get('temp'))
self.Temp.set(self.temperature)
self.after(3000, self.Temperatur)
def WeatherInfo(self):
self.Weather = tk.StringVar()
self.weather = str(self.weatherdata.get_detailed_status())
if self.weather in self.dictionary:
self.weather = self.dictionary[self.weather]
self.weatherstring = self.weather.title()
self.Weather.set(self.weatherstring)
self.after(3000, self.WeatherInfo)
def Display(self):
self.Temperatur()
self.WeatherInfo()
self.configure(text=self.Weather.get() + ", " + self.Temp.get() + "°", bg='black',
fg='#FFFF96', font=("arial, 35"))
self.after(3000, self.Display)
Now, what should happen is the widget updating every 3 secs (just to debug), although I know even if updating works it won't change every 3 seconds, 'cause you know...weather doesn't change every 3 secs.
As done in suggestion of Idlehands the problem was not in the Label or the updating.
The Code, if written that way, would call .get_weather only once, thus creating a stagnating variable. I added a method that updates the pyowm parsing and now evertyhing's working fine!
I have two GUIs and I want these guis to be able communicate together. I used Matlab in the past and in Matlab I was using addlistener and basically communicate between multiple guis. I am new to python and I want when I am clicking on the show button on my second gui it update the axes on my first gui. Basically, plot the image on the other gui based on the path I choose on another.
Here is the image for better understanding
Here is the code:
from tkinter import *
from PIL import Image, ImageTk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import
FigureCanvasTkAgg
import PySimpleGUI as sg
import tkinter.filedialog as fdialog
from natsort import natsorted
import os
import cv2
class MyCanvas(Canvas):
def __init__(self, parent=None, img=None, *parms, **kparms):
Canvas.__init__(self, parent, *parms, **kparms)
self._width = 20;
self._height = 10;
self._starting_drag_position = ()
self.config(width=self._width, height=self._height, bg='white')
self._draw_some_example_objects()
self.pack(fill=BOTH, expand=YES)
def _draw_some_example_objects(self):
self.fig = Figure()
gs = self.fig.add_gridspec(5, 2)
self.axis= self.fig.add_subplot(gs[0:4, 0])
self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.canvas.get_tk_widget().pack(side="top", fill='both', expand=True)
colors = dict(outline="black")
class MyGUI(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
self.title("Drag canvas with mouse")
self.geometry("700x700")
"""For some reason menu should be added here"""
self.menuBar = Menu(master=self)
self.filemenu = Menu(self.menuBar, tearoff=0)
self.filemenu.add_command(label="listview!", command=self.list)
self.menuBar.add_cascade(label="File", menu=self.filemenu)
self.config(menu=self.menuBar)
self._addWidgets()
def _addWidgets(self):
my_canvas = MyCanvas(self)
def list(self):
listView(self)
def listView(self):
sg.ChangeLookAndFeel('GreenTan')
dir = fdialog.askdirectory()
filesList = os.listdir(dir)
filesList = natsorted(filesList)
layout = [
[sg.Listbox(values=(filesList), size=(60, 30), key='_IN_')],
[sg.Button('Show')]
]
window = sg.Window('Everything bagel', default_element_size=(40, 1), grab_anywhere=False).Layout(layout)
while True:
event, values = window.Read()
if event is None or event == 'Exit':
break
print(dir + values.get('_IN_')[0])
if __name__ == '__main__':
MyGUI().mainloop()`
Take a look at this demo program.
Drop your code into the section where it says:
#------------------------ PASTE YOUR MATPLOTLIB CODE HERE ----------------------
Make sure your drawing is in the variable 'fig'. It will create a window with GUI options and your Matplotlib plot embedded in it.
I'm a beginner trying to use tkinter to create a GUI. I've adapted code from other sources for my own use.
At the moment I'm trying to display an image along with a button.
However, when running the code, only the image displays no matter where I move the button, the button isn't displayed.
Would be grateful for any help.
Additional functionality:
I'm looking to implement a function so that I can move the image around using coordinates, also a function that allows me to use .jpg instead of .png.
Thanks in advance!
Code:
from tkinter import *
from PIL import Image, ImageTk
class Window(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def init_window(self):
self.master.title("WindowName")
self.pack(fill = BOTH, expand = 1)
quitButton = Button(self, text = "quit", command=self.client_exit)
quitButton.place(x=400,y=400)
self.showImg()
def client_exit(self):
exit() # Or another function
def showImg(self):
self.grid(row=0)
self.columnconfigure(0,weight=1)
self.rowconfigure(0,weight=1)
self.original = Image.open('wire.png')
resized = self.original.resize((200, 200),Image.ANTIALIAS)
self.image = ImageTk.PhotoImage(resized)
self.display = Label(self, image = self.image)
self.display.grid(row=0)
root = Tk()
root.geometry("600x600")
app = Window(root)
root.mainloop()
I am trying to display the animation from my gif image. From my previous question, I discovered that Tkinter doesn't animate images automatically. My Tk interface shows the first frame of the image, and when I click the button to play its animation, it does nothing. It's likely something to do with the command associated with the button. Here's the code:
from Tkinter import *
import Tkinter
root = Tk()
photo_path = "/users/zinedine/downloads/091.gif"
photo = PhotoImage(
file = photo_path
)
def run():
frame = 1
while True:
try:
photo = PhotoImage(
file = photo_path,
format = "gif - {}".format(frame)
)
frame = frame + 1
except Exception: # This because I don't know what exception it would raise
frame = 1
break
picture = Label(image = photo)
picture.pack()
picture.configure(run())
animate = Button(
root,
text = "animate",
command = run()
)
animate.pack()
root.geometry("250x250+100+100")
root.mainloop()
You can use the universal Tk widget after() method to schedule a function to run after a specified delay given in milliseconds. This only happens once, so typically the function itself also calls after() to perpetuate the process.
In the code below a custom AnimatedGif container class is defined which loads and holds all the frames of animated sequence separately in a list which allows quick (random) access to them using [] indexing syntax. It reads individual frames from the file using the -index indexvalue image format suboption mentioned on the photo Tk manual page.
I got the test image shown below from the Animation Library website.
Here's how things should look when it's initially started.
You should be able use the same technique to animate multiple images or those that are attached to other kinds of widgets, such as Button and Canvas instances.
try:
from tkinter import *
except ImportError:
from Tkinter import * # Python 2
class AnimatedGif(object):
""" Animated GIF Image Container. """
def __init__(self, image_file_path):
# Read in all the frames of a multi-frame gif image.
self._frames = []
frame_num = 0 # Number of next frame to read.
while True:
try:
frame = PhotoImage(file=image_file_path,
format="gif -index {}".format(frame_num))
except TclError:
break
self._frames.append(frame)
frame_num += 1
def __len__(self):
return len(self._frames)
def __getitem__(self, frame_num):
return self._frames[frame_num]
def update_label_image(label, ani_img, ms_delay, frame_num):
global cancel_id
label.configure(image=ani_img[frame_num])
frame_num = (frame_num+1) % len(ani_img)
cancel_id = root.after(
ms_delay, update_label_image, label, ani_img, ms_delay, frame_num)
def enable_animation():
global cancel_id
if cancel_id is None: # Animation not started?
ms_delay = 1000 // len(ani_img) # Show all frames in 1000 ms.
cancel_id = root.after(
ms_delay, update_label_image, animation, ani_img, ms_delay, 0)
def cancel_animation():
global cancel_id
if cancel_id is not None: # Animation started?
root.after_cancel(cancel_id)
cancel_id = None
root = Tk()
root.title("Animation Demo")
root.geometry("250x125+100+100")
ani_img = AnimatedGif("small_globe.gif")
cancel_id = None
animation = Label(image=ani_img[0]) # Display first frame initially.
animation.pack()
Button(root, text="start animation", command=enable_animation).pack()
Button(root, text="stop animation", command=cancel_animation).pack()
Button(root, text="exit", command=root.quit).pack()
root.mainloop()
Here's an alternative version of my previous answer. Although also based on the universal Tk widget after() method, it uses the PIL (or the pillow fork of it) module to read the gif image file. With PIL it's not only easy to extract each frame from the file, but also to get the delay (or "duration") between frames of the animation directly from the gif file — which eliminates guessing what it should be for different files.
try:
from tkinter import *
except ImportError:
from Tkinter import *
from PIL import Image, ImageSequence, ImageTk
class AnimatedGif(object):
""" Animated GIF Image Container. """
def __init__(self, image_file_path):
# Read in all the frames of a multi-frame gif image.
self._frames = []
img = Image.open(image_file_path)
for frame in ImageSequence.Iterator(img):
photo = ImageTk.PhotoImage(frame)
photo.delay = frame.info['duration'] * 10 # Add attribute.
self._frames.append(photo)
def __len__(self):
return len(self._frames)
def __getitem__(self, frame_num):
return self._frames[frame_num]
def update_label_image(label, ani_img, frame_num):
""" Change label image to given frame number of AnimatedGif. """
global cancel_id
frame = ani_img[frame_num]
label.configure(image=frame)
frame_num = (frame_num+1) % len(ani_img) # Next frame number.
cancel_id = root.after(frame.delay, update_label_image, label, ani_img, frame_num)
def enable_animation():
""" Start animation of label image. """
global cancel_id
if cancel_id is None: # Animation not started?
cancel_id = root.after(ani_img[0].delay, update_label_image, animation, ani_img, 0)
def cancel_animation():
""" Stop animation of label image. """
global cancel_id
if cancel_id is not None: # Animation started?
root.after_cancel(cancel_id)
cancel_id = None
root = Tk()
root.title("Animation Demo")
root.geometry("250x125+100+100")
ani_img = AnimatedGif("small_globe.gif")
cancel_id = None
animation = Label(image=ani_img[0]) # Display first frame initially.
animation.pack()
Button(root, text="start animation", command=enable_animation).pack()
Button(root, text="stop animation", command=cancel_animation).pack()
Button(root, text="exit", command=root.quit).pack()
root.mainloop()