Python Tkinter Display Loading Animation While Performing Certain Tasks - python

I have located this useful code for Tkinter animations from https://www.daniweb.com/programming/software-development/threads/396918/how-to-use-animated-gifs-with-tkinter ,supplied by "vegaseat".
I have adapted a similar design for displaying gifs animations to a project. I am wishing to implement this as a function to certain areas of a script, e.g. importing modules etc. I have tried a few approaches but when I called this as a function, it first runs the animation and then imports the module (as we would expect).
I guess I am exploring ways to get this to work concurrently...while the script is importing modules( or running another process where I wish to display the animation), the animation would be displayed, and then disappear, until the next call. Suggestions would be appreciated.
Thanks a lot.
# mimic an animated GIF displaying a series of GIFs
# an animated GIF was used to create the series of GIFs
# with a common GIF animator utility
import time
from Tkinter import *
root = Tk()
imagelist = ["dog001.gif","dog002.gif","dog003.gif",
"dog004.gif","dog005.gif","dog006.gif","dog007.gif"]
# extract width and height info
photo = PhotoImage(file=imagelist[0])
width = photo.width()
height = photo.height()
canvas = Canvas(width=width, height=height)
canvas.pack()
# create a list of image objects
giflist = []
for imagefile in imagelist:
photo = PhotoImage(file=imagefile)
giflist.append(photo)
# loop through the gif image objects for a while
for k in range(0, 1000):
for gif in giflist:
canvas.delete(ALL)
canvas.create_image(width/2.0, height/2.0, image=gif)
canvas.update()
time.sleep(0.1)
root.mainloop()
EDIT: I am attempting to implement the code,below, per some helpful suggestions. The goal is to begin the animation, while the application is importing the modules in the "IMPORTS" function, and then have it destroyed after the imports are completed.
# Import modules
from Tkinter import *
from PIL import ImageTk
from PIL import Image
import os,time
from os.path import dirname
from os.path import join
def IMPORTS():
import tkMessageBox
from ttk import Combobox
import csv,datetime
import xlrd,xlwt
import getpass
import traceback
import arcpy
from arcpy import AddMessage
import win32com.client
inGif = #root image (.gif)
FramesFolder = #Folder containing frames of the root image
W=Toplevel()
W.wm_overrideredirect(True) # I wish to only display the widget spinning without the window frame
imagelist = [os.path.join(FramesFolder,s) for s in os.listdir(FramesFolder) if not s.endswith('db')]
# extract width and height info
photo = PhotoImage(file=imagelist[0])
width = photo.width()
height = photo.height()
canvas = Canvas(W,width=width, height=height)
canvas.pack()
# create a list of image objects
giflist = []
for imagefile in imagelist:
photo = PhotoImage(file=imagefile)
giflist.append(photo)
timer_id = None
def start_loading(n=0):
global timer_id
gif = giflist[n%len(giflist)]
canvas.create_image(gif.width()//2, gif.height()//2, image=gif)
timer_id = W.after(100, start_loading, n+1) # call this function every 100ms
def stop_loading():
if timer_id:
W.after_cancel(timer_id)
canvas.delete(ALL)
start_loading()
IMPORTS()
stop_loading()
# The spinning widget should be completely destroyed before moving on...
It is returning
"NameError: name 'tkMessageBox' is not defined"

You can use Tk.after() and Tk.after_cancel() to start and stop the animation:
timer_id = None
def start_loading(n=0):
global timer_id
gif = giflist[n%len(giflist)]
canvas.create_image(gif.width()//2, gif.height()//2, image=gif)
timer_id = root.after(100, start_loading, n+1) # call this function every 100ms
def stop_loading():
if timer_id:
root.after_cancel(timer_id)
canvas.delete(ALL)
Then, you can call start_loading() before the long process and call stop_loading() after the long process:
start_loading()
long_process() # your long process
stop_loading()

Related

My code works with downloaded gifs, but not with the one I made, may anyone help me?

So I saw this code on YouTube, and wanted to try it, since I need animated gif in my program, I made a throwaway file to test if it works, and it works, but only on downloaded gifs. Though, I don't need downloaded one, but the one I made. I have added the gif that work as an attachment, but mine is a few KB bigger than the limit(https://i.stack.imgur.com/Sfv7d.gif).
When I added my gif in the file=".gif" line, and program was run, only the console appeared, and root wasn't visible, but with the other one, everything worked as how it should have. If it is of any help, my gif is quite large dimension wise(1600x900px).
Here is the code:
import tkinter as tk
from PIL import Image
root = tk.Tk()
file="star.gif"
info = Image.open(file)
frames = info.n_frames # gives total number of frames that gif contains
# creating list of PhotoImage objects for each frames
im = [tk.PhotoImage(file=file,format=f"gif -index {i}") for i in range(frames)]
count = 0
anim = None
def animation(count):
global anim
im2 = im[count]
gif_label.configure(image=im2)
count += 1
if count == frames:
count = 0
anim = root.after(50,lambda :animation(count))
def stop_animation():
root.after_cancel(anim)
gif_label = tk.Label(root,image="")
gif_label.pack()
start = tk.Button(root,text="start",command=lambda :animation(count))
start.pack()
stop = tk.Button(root,text="stop",command=stop_animation)
stop.pack()
root.mainloop()
Any help would be great, and thank you in advance!!
Here is the gif that won't work:
Gif that doesnt work
The provided not working GIF actually works, but it takes time to load all the frames. It takes around 70 seconds in my PC with AMD Ryzen 9 CPU.
It is a lot faster if using Pillow module:
...
from PIL import Image, ImageTk, ImageSequence
...
im = [ImageTk.PhotoImage(img) for img in ImageSequence.Iterator(info)]
...
It takes only around 0.2 seconds.
So after all of this, I decided to change up the method, and got another way for playing gifs. Just so you know it still prints out exceptions, so you can remove those few lines. Here is the code for anyone who needs it:
from pygame import mixer
from tkinter import *
from tkinter import ttk
import tkinter.font as TkFont
from array import *
import time
import random
from PIL import ImageTk, Image
from itertools import count
MainMenu=Tk(className=" U-boot Sinker")
MainMenu.geometry("1600x900+150+75")
MainMenu.resizable(False,False)
images_list = []
def extract_image_from_gif(path):
global gif_duration
image= Image.open(path)
for r in count(1):
try:
images_list.append(image.copy())
image.seek(r)
except Exception as e:
print(e)
break
print(len(images_list))
x=0
def play_gif():
global x, cur_img
try:
x+=1
cur_img = ImageTk.PhotoImage(images_list[x])
gif_lb.config(image=cur_img)
MainMenu.after(10, play_gif)
except Exception as e:
print(e)
x=0
MainMenu.after(10, play_gif)
gif_lb = Label(MainMenu)
gif_lb.pack()
extract_image_from_gif('StartScreen.gif')
play_gif()
MMCan=Canvas(MainMenu, width=1600, height=900)
MMCan.pack()
MainMenu.mainloop()
All in all thank you everyone that tried to help me, and lost braincells in process!

Display All PNG Files of Your Current Folder with Tkinter

Today i was Trying to Display All .png files in my current Directory In a GUI With Python and Tkinter but The Code Was Not Wroking in a Way That i expected It only Displays The Last Image of the Folder and The Space for Other Images Was Blank.
# Importing Essentials
import os
from tkinter import Tk, Label, PhotoImage
# Initialize Tk root Class
root = Tk()
# Set Default Size of Our Tkinter Window - (WidthxHeight)
root.geometry("200x910")
# Set Minimum Size of Our Window - (Width, Height)
root.minsize(350, 200)
# Add a Label
WelcomeLabel = Label(text="All Images of This Folder")
WelcomeLabel.pack()
# Get All PNG Files in a List name pngFiles
pngFiles = []
for item in os.listdir():
if item.endswith(".png"):
pngFiles.append(item)
# Display All The PNG files to our GUI Screen
for file in pngFiles:
photoLoaded = PhotoImage(file=file)
mainImageLabel = Label(image=photoLoaded)
mainImageLabel.pack()
# Run The MainLoop
root.mainloop()
In tkinter the images must have references in memory, you cannot overwrite and reuse them, my solution is to make a list using pathlib, and then create the labels
If you display an image inside a function, then make sure to keep reference to the > image object in your Python program, either by storing it in a global variable or > by attaching it to another object.
Basics For Displaying Image In Tkinter Python
import pathlib
from tkinter import Tk, Label, PhotoImage
root = Tk()
root.geometry("200x910")
root.minsize(350, 200)
wellcome = Label(text="All Images of This Folder")
wellcome.pack()
# create a list from images with memory reference
images_with_ref = []
for img in pathlib.Path('.').glob('*.png'):
photoLoaded = PhotoImage(file=img)
images_with_ref.append(photoLoaded)
# create labels with correct images
for image in images_with_ref:
Label(image=image).pack()
root.mainloop()
With the current code you are overwriting your photoLoaded with every loop.
Instead you have can pack these photoLoaded in a list.
Additionally working with grids is recommended when working with multiple items.
both things are implemented in the code below:
# Importing Essentials
import os
from tkinter import Tk, Label, PhotoImage
# Initialize Tk root Class
root = Tk()
# Set Default Size of Our Tkinter Window - (WidthxHeight)
root.geometry("200x910")
# Set Minimum Size of Our Window - (Width, Height)
root.minsize(350, 200)
# Add a Label
WelcomeLabel = Label(text="All Images of This Folder")
WelcomeLabel.grid(row=0)
# Get All PNG Files in a List name pngFiles
pngFiles = []
for item in os.listdir():
if item.endswith(".png"):
pngFiles.append(item)
print(pngFiles)
# Display All The PNG files to our GUI Screen
i = 0
pics = []
for file in pngFiles:
pics.append(PhotoImage(file=file))
Label(root, image=pics[i]).grid(row=i + 1)
i += 1
# Run The MainLoop
root.mainloop()

How can I close an image in a tkinter window without closing the window?

I am writing an application that consists of three tkinter windows on the same page: a calendar, a notepad and a 'picture of the day' which is fetched from a bank of images when initiated by the user. The image is named after the calendar date + .jpg. It works fine...the first time. When I select another date and retrieve the image for that date, it comes up behind the first image in the 'picture of the day' window. The problem is that the first image does not disappear when replaced by another. I do not want to close the window, just close the current picture and replace it by the new one. There might a simple way, but I spent hours searching for it. The code below of part of the application. Hopefully, it shows where the problem is. Can you point me in the right direction? Thanks
from tkinter import *
from PIL import ImageTk, Image
from tkinter import filedialog
import os
photo = Toplevel()
photo.geometry("300x250+1300+150")
photo.resizable(width=True, height=True)
photo.attributes("-topmost", True)
def openfn(): # To go fetch a new image
filename = filedialog.askopenfilename(title='open')
return filename
root.destroy()
def open_img(): # To open a new image
x = openfn()
img = Image.open(x)
img = img.resize((225,200), Image.ANTIALIAS)
im = ImageTk.PhotoImage(img)
panel = Label(photo, image=im)
panel.image = im
panel.pack()
img=img.save(cal.calphoto) # Saves the image (calphoto is date + .jpg)
def retrieve_photo(): # To open an existing image
img=Image.open(cal.calphoto)
im = ImageTk.PhotoImage(img)
panel = Label(photo, image=im)
panel.image = im
panel.pack()
I changed a few things in your code. The main thing is the take the label with the image into the global scope so the function open_img() can make changes to it instead of creating a new label each time it is called. Below open_img() now configures the label to show the image selected each time it is called. (This is basically illustrating what Delrius said.)
from tkinter import *
from PIL import ImageTk, Image
from tkinter import filedialog
import os
photo = Toplevel()
photo.geometry("300x250+1300+150")
photo.resizable(width=True, height=True)
photo.attributes("-topmost", True)
panel = Label(photo) # panel Label moved into global scope so the
# functions below can access it
def openfn():
filename = filedialog.askopenfilename(title='open')
return filename
root.destroy()
def open_img():
x = openfn()
img = Image.open(x)
img = img.resize((225,200), Image.ANTIALIAS)
im = ImageTk.PhotoImage(img)
panel.config(image=im) # panel label is configured to show image selected
panel.image = im
panel.pack()

Not being able to load an image in python

I am trying to load an image into a canvas in python. However I am getting the error: TclError: couldn't recognize data in image file "C:\testimage\spongesea.jpg"
import Tkinter
from Tkinter import *
import time
import inspect
import os
from PIL import Image, ImageTk
class game_one:
def __init__(self):
global root
global canvas_one
root = Tk()
root.title(" Thanks Josh Dark
canvas_one = Tkinter.Canvas(root, bg="BLACK")
canvas_one.pack(expand= YES, fill= BOTH)
canvas_one.focus_set() #allows keyboard events
p = PhotoImage(file="C:\testimage\spongesea.jpg")
canvas_one.create_image(0, 0, image = p, anchor=NW)
root.grab_set()#I forget what this does. Don't change it.
root.lift()#This makes root appear in front of the other applications
ObjectExample = game_one()# starts the animation
I can open the image manually from the file, so it is not corrupted, and it is calling the correct place. Any ideas? Thanks
PhotoImage works only with GIF and PGM/PPM.
You have to use Image, ImageTk to work with other formats
from PIL import Image, ImageTk
image = Image.open("lenna.jpg")
photo = ImageTk.PhotoImage(image)
BTW: read PhotoImage and see "Garbage Collection problem" in note.

Randomly displaying images with one window with Tkinter

I am trying to display images in a directory randomly in a single window that changes every 3 seconds. I also want it to be cross platform, as I am developing in Windows, but it will run on linux.
Currently I have this working code that iterates through all the image files of a directory with a mouse click (Code Below)
import os, sys, Tkinter, Image, ImageTk
def button_click_exit_mainloop (event):
event.widget.quit()
root = Tkinter.Tk()
root.bind("<Button>", button_click_exit_mainloop)
root.geometry('+%d+%d' % (-5,-5)) #controls where the window is
#gets list of file names in certain directory. In this case, the directory it is in
dirlist = os.listdir('.')
for f in dirlist:
try:
image1 = Image.open(f)
root.geometry('%dx%d' % (image1.size[0],image1.size[1]))
tkpi = ImageTk.PhotoImage(image1)
label_image = Tkinter.Label(root, image=tkpi)
label_image.place(x=0,y=0,width=image1.size[0],height=image1.size[1])
root.mainloop() # wait until user clicks the window
except Exception, e:
pass
The way it does this however is when the mouse clicks on the window it calls a function to close the widget.
The problem I am having is how to call this function, or close the widget without an event. Any suggestions?
This is what I currently have. This doesn't work obviously because it is stuck in the root.mainloop(), but it shows what I have generally in mind (Code below)
import os, sys, Tkinter, Image, ImageTk, random
root = Tkinter.Tk()
root.geometry('+%d+%d' % (-5,-5)) #controls where the window is
#gets list of file names in certain directory. In this case, the directory it is in
dirlist = os.listdir('.') #might not be in order, CHECK!!!
while True:
randInt = random.randint(0, 1)
image = Image.open(dirlist[randInt])
root.geometry('%dx%d' % (image.size[0],image.size[1]))
tkpi = ImageTk.PhotoImage(image)
label_image = Tkinter.Label(root, image=tkpi)
label_image.place(x=0,y=0,width=image.size[0],height=image.size[1])
root.mainloop()
time.sleep(3)
Thank you!
-Jonathan
EDIT: Response to Bryan Oakley:
I tried what you suggested, and this looks like the solution.
The function is being called every 3 seconds, and a window is being created, but the image is not being placed in the window.
Is it that I do not have access to the root? How do I gain access?
Here is what I have currently:
import os, sys, Tkinter, Image, ImageTk, random
def changeImage():
#gets list of file names in certain directory. In this case, the directory it is in
dirlist = os.listdir('.') #might not be in order, CHECK!!!
#get random image
randInt = random.randint(0, 1)
image = Image.open(dirlist[randInt])
#set size to show, in this case the whole picture
root.geometry('%dx%d' % (image.size[0],image.size[1]))
#Creates a Tkinter compatible photo image
tkpi = ImageTk.PhotoImage(image)
#Put image in a label and place it
label_image = Tkinter.Label(root, image=tkpi)
label_image.place(x=0,y=0,width=image.size[0],height=image.size[1])
# call this function again in three seconds
root.after(3000, changeImage)
root = Tkinter.Tk()
root.geometry('+%d+%d' % (-5,-5)) #controls where the window is
changeImage()
root.mainloop()
Thank you!!
SOLUTION EDIT:
I did not change the code so the label is only created once, so a label is created with each call. I did not do this because this could be applied to many other variables (dirlist = os.listdir('.') for exmaple), but would make the code harder to read. I did not see any disadvantage other than maybe more cycles used? I did not see a memory increase over time, which is all that mattered to me.
Here is the code, thank you Bryan Oakley for helping me!!
import os, Tkinter, Image, ImageTk, random
def changeImage():
global tkpi #need global so that the image does not get derefrenced out of function
#gets list of file names in certain directory. In this case, the directory it is in
dirlist = os.listdir('.')
#get random image
randInt = random.randint(0, 1)
image = Image.open(dirlist[randInt])
#set size to show, in this case the whole picture
root.geometry('%dx%d' % (image.size[0],image.size[1]))
#Creates a Tkinter compatible photo image
tkpi = ImageTk.PhotoImage(image)
#Put image in a label and place it
label_image = Tkinter.Label(root, image=tkpi)
label_image.place(x=0,y=0,width=image.size[0],height=image.size[1])
# call this function again in 1/2 a second
root.after(500, changeImage)
tkpi = None #create this global variable so that the image is not derefrenced
root = Tkinter.Tk()
root.geometry('+%d+%d' % (-5,-5)) #controls where the window is
changeImage()
root.mainloop()
You need to remove your infinite loop -- tkinter already has one built-in. Instead, use after to periodically call a function:
def changeImage():
<do whatever you want, such as swapping out images>
# call this function again in three seconds
root.after(3000, changeImage)
Then, in your main program you would call the function before calling mainloop:
root = Tkinter.Tk()
...
changeImage()
root.mainloop()
Also, you don't need changeImage to create a new label widget every time -- create the label once outside the function, and just change the image each time it is called.
All credit goes to BryanOakley for finding the root cause of the error. I cleaned up your code slightly. The imports also changed a little since I'm running Python3, but wanted to make sure it would work.
import os, sys, tkinter, random # N.B. tkinter not Tkinter in py3
from PIL import Image, ImageTk # these are submodules in pillow py3
class Root(tkinter.Tk): # I buried this in a class. I prefer that for tkinter
def __init__(self):
super().__init__() # call Tk.__init__
self.CYCLEDELAY = 3000 # 3 second per cycle
# create and place the label once.
self.image_label = tkinter.Label(self)
self.image_label.place(x=0,y=0)
# call our function
self.changeImage()
def changeImage(self):
"""Change the image on a delay"""
dirlist = os.listdir('.')
image = Image.open(random.choice(dirlist))
# you had a funky method of getting a random member here. I cleaned it up
i_width, i_height = image.size
self.geometry("{}x{}".format(i_width,i_height))
# change root's geometry using string formatting (preferred)
self.image_label.configure(width=i_width, height=i_height)
# change the label's geometry using string formatting
self.tkpi = ImageTk.PhotoImage(image)
self.image_label.configure(image=self.tkpi)
# configure the label to use the PhotoImage
self.after(self.CYCLEDELAY,self.changeImage)
# loop!
root = Root()
root.mainloop()

Categories