I'm writing a slideshow program with Tkinter, but I don't know how to go to the next image without binding a key.
import os, sys
import Tkinter
import Image, ImageTk
import time
root = Tkinter.Tk()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.overrideredirect(1)
root.geometry("%dx%d+0+0" % (w, h))
root.focus_set()
root.bind("<Escape>", lambda e: e.widget.quit())
image_path = os.path.join(os.getcwd(), 'images/')
dirlist = os.listdir(image_path)
for f in dirlist:
try:
image = Image.open(image_path+f)
tkpi = ImageTk.PhotoImage(image)
label_image = Tkinter.Label(root, image=tkpi) # ?
label_image.place(x=0,y=0,width=w,height=h)
root.mainloop(0)
except IOError:
pass
root.destroy()
I would like to add a time.sleep(10) "instead" of the root.mainloop(0) so that it would go to the next image after 10s. Now it changes when I press ESC. How can I have a timer there?
edit: I should add that I don't want another thread that does a sleep even though it works.
You can try
root.after(10*1000, root.quit)
There's no need to do a loop over your images -- you're already running in a loop (mainloop) so take advantage of it. The typical way to do this is to create a method that draws something, waits for a period of time, then calls itself. This isn't recursion, it's just telling the main loop "after N seconds, call me again".
Here's a working example:
import glob
import Tkinter
class Slideshow:
def __init__(self, pattern="*.gif", delay=10000):
root = Tkinter.Tk()
root.geometry("200x200")
# this label will be used to display the image. Make
# it automatically fill the whole window
label = Tkinter.Label(root)
label.pack(side="top", fill="both", expand=True)
self.current_image = None
self.image_label = label
self.root = root
self.image_files = glob.glob(pattern)
self.delay = delay # milliseconds
# schedule the first image to appear as soon after the
# the loop starts as possible.
root.after(1, self.showImage)
root.mainloop()
def showImage(self):
# display the next file
file = self.image_files.pop(0)
self.current_image = Tkinter.PhotoImage(file=file)
self.image_label.configure(image=self.current_image)
# either reschedule to display the file,
# or quit if there are no more files to display
if len(self.image_files) > 0:
self.root.after(self.delay, self.showImage)
else:
self.root.after(self.delay, self.root.quit)
def quit(self):
self.root.quit()
if __name__ == "__main__":
app=Slideshow("images/*.gif", 1000)
Related
This is a photo slideshow code its just a skeleton I want use buttons to start or stop the code from slide show.
# import required modules
import tkinter as tk
from tkinter import *
from PIL import Image
from PIL import ImageTk
# This here is to adjust window
root=tk.Tk()
root.geometry("200x200")
# loading the images
img=ImageTk.PhotoImage(Image.open("photo1.png"))
img2=ImageTk.PhotoImage(Image.open("photo2.png"))
img3=ImageTk.PhotoImage(Image.open("photo3.png"))
img4=ImageTk.PhotoImage(Image.open("photo4.png"))
l=Label()
l.pack()
# using recursion to slide to next image
x = 1
# function to change to next image
def move():
global x
if x == 4:
x = 1
if x == 1:
l.config(image=img)
elif x == 2:
l.config(image=img2)
elif x == 3:
l.config(image=img3)
x = x+1
root.after(2000, move)
# calling the function please refer the indents
btn_1 = Button(root, text="start", command=move)
btn_1.pack()
btn_2=Button(root,text="start", command=pause))
btn_2.pack()
root.mainloop()
Here's runnable code that illustrates how to do it. It uses several global variables to keep track of the current state of the slide show that the various functions use to determine what needs to be be done.
It simplies the logic you had for determining the next image to display by adding one to then current image index and the applying the modulo % the number of images in show which forces the index back to 0 whenever it exceeds the number of image that there are — which can all be done with a single statement: cur_img = (cur_img+1) % len(slides).
You could get rid of the most of the globals by defining a SlideShow class that encapsulated the state variables and defined the related functions that manipulate them.
from pathlib import Path
from PIL import Image
from PIL import ImageTk
import tkinter as tk
root = tk.Tk()
root.geometry("500x500")
after_id = None
cur_img = 0
paused = True
image_folder = Path('./photos')
slides = [ImageTk.PhotoImage(Image.open(filename))
for filename in image_folder.glob('*.png')]
def slide_show():
"""Change to next image (wraps around)."""
global after_id, cur_img, paused
if not paused:
cur_img = (cur_img+1) % len(slides)
lbl.config(image=slides[cur_img])
after_id = root.after(2000, slide_show)
def start():
global after_id, cur_img, paused
paused = False
if after_id: # Already started?
root.after_cancel(after_id)
after_id = None
slide_show()
def pause():
global after_id, cur_img, paused
paused = True
lbl = tk.Label(image=slides[cur_img])
lbl.pack()
btn_1 = tk.Button(root, text="start", command=start)
btn_1.pack()
btn_2 = tk.Button(root, text="pause", command=pause)
btn_2.pack()
root.mainloop()
when i start the process def loading(): has come out until print("hello world") and also without click button in def loading(): when i start the def loading(): it must be click button and use command with button but i dont wanna do like that lastly i just want to make when i start process def loading(): has come out first until print("hello world") without any click button or something clicking please help me
from PIL import Image
def hi():
time.sleep(10)
print("hello world")
def loading():
root = tk.Tk()
root.configure(bg='white')
file="giphy.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 += 2
if count == frames:
count = 0
anim = root.after(100,lambda :animation(count))
gif_label = tk.Label(root,image="",bg='white',)
gif_label.pack()
The code below shows a way to do it. When the def hi() function executes and print the message, it now also sets a global flag variable which the function doing the animation checks periodically. When the value of the flag has changed, it causes the animation to cease.
from PIL import Image
import tkinter as tk
stop = False # Global flag.
def hi():
global stop
print("hello world")
stop = True
def loading(filename):
root = tk.Tk()
root.configure(bg='white')
info = Image.open(filename)
frames = info.n_frames # gives total number of frames that gif contains
# creating list of PhotoImage objects for each frames
im = [tk.PhotoImage(file=filename, format=f"gif -index {i}") for i in range(frames)]
count = 0
def animation(count):
frame = im[count]
gif_label.configure(image=frame)
count = (count+1) % frames
if not stop:
root.after(200, lambda: animation(count))
btn = tk.Button(text="Start", command=lambda: animation(count))
btn.pack()
gif_label = tk.Label(root, image="", bg='white',)
gif_label.pack()
root.after(5000, hi) # Call hi() in 5 seconds.
root.mainloop()
loading("small_globe.gif")
In my code, I am trying to make a loading screen for a frogger game but for some reason I am encountering a problem where I display a picture and then do the .sleep function before displaying a label over the top of it however it displays both of them at the same time it just runs the code 1 second after it should, can anyone help?
Here is my code below:
from tkinter import *
import tkinter as tk
import time
window = Tk()
window.geometry("1300x899")
LoadingScreen = PhotoImage(file = "FroggerLoad.gif")
Loading = Label(master = window, image = LoadingScreen)
Loading.pack()
Loading.place(x = 65, y = 0)
time.sleep(1)
FroggerDisplay = Label(master = window, font ("ComicSans",100,"bold"),text = "Frogger")
FroggerDisplay.pack()
FroggerDisplay.place(x = 500, y = 300)
window.mainloop()
When you use time.sleep(1) before starting the window.mainloop(), the window is created only after 1 second, and the FroggerDisplay label will be created at the same time with it. So, you can't use time.sleep(seconds) now.However, you can use window.after(ms, func) method, and place into the function all the code between time.sleep(1) and window.mainloop(). Note, that unlike the time.sleep(seconds) you must give the time to window.after (the first argument) as milliseconds.Here is the edited code:
from tkinter import *
def create_fd_label():
frogger_display = Label(root, font=("ComicSans", 100, "bold"), text="Frogger") # create a label to display
frogger_display.place(x=500, y=300) # place the label for frogger display
root = Tk() # create the root window
root.geometry("1300x899") # set the root window's size
loading_screen = PhotoImage(file="FroggerLoad.gif") # create the "Loading" image
loading = Label(root, image=loading_screen) # create the label with the "Loading" image
loading.place(x=65, y=0) # place the label for loading screen
root.after(1000, create_fd_label) # root.after(ms, func)
root.mainloop() # start the root window's mainloop
PS: 1) Why do you use .pack(...) and then .place(...) methods at the same time - the first one (.pack(...) here) will be ignored by Tkinter.
2) It's better to use a Canvas widget for creating a game - unlike labels it supports transparency and simpler to use. For example:
from tkinter import *
root = Tk() # create the root window
root.geometry("1300x899") # set the root window's size
canv = Canvas(root) # create the Canvas widget
canv.pack(fill=BOTH, expand=YES) # and pack it on the screen
loading_screen = PhotoImage(file="FroggerLoad.gif") # open the "Loading" image
canv.create_image((65, 0), image=loading_screen) # create it on the Canvas
root.after(1000, lambda: canv.create_text((500, 300),
font=("ComicSans", 100, "bold"),
text="Frogger")) # root.after(ms, func)
root.mainloop() # start the root window's mainloop
Note: you might need to change coords with Canvas.
I want to control the Speed of the images animation using the slider. So im trying to do the following but instead I get many images reappearing on the same canvas when i vary the slider. I want to vary the time interval between one image and the next using the slider.
from tkinter import *
#import tkFont
import random
from time import sleep
root = Tk()
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
scale = Scale(frame, from_=0, to=100, command=self.update)
scale.grid(row=0)
def update(self, duty):
#Importing the images. They are named a1.gif, a2.gif...a7.gif
frame=[]
for i in range(1,10):
fname="CORE\\a"+str(i)+".gif"
frame+=[PhotoImage(file=fname)]
wrap = Canvas(root, width=200, height=140)
wrap.pack()
def do_animation(currentframe):
def do_image():
wrap.create_image(100,70,image=frame[currentframe], tag='ani')
# Delete the current picture if one exists
wrap.delete('ani')
try:
do_image()
except IndexError:
# End of image list reached, start over at the first image
#- works for an arbitrary number of images
currentframe = 0
do_image()
wrap.update_idletasks() #Force redraw
currentframe = currentframe + 1
# Call myself again to keep the animation running in a loop
root.after(100, do_animation, currentframe)
# Start the animation loop just after the Tkinter loop begins
root.after(100, do_animation, 0)
app = App(root)
#app.geometry("800x480")
root.mainloop()
python
You're creating a new canvas every time, and not using the speed from the slider. I think this is what you want.
from tkinter import *
#import tkFont
import random
from time import sleep
root = Tk()
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.scale = Scale(frame, from_=0, to=1000)
self.scale.grid(row=0)
self.wrap = Canvas(root, width=200, height=140)
self.wrap.pack()
self.update()
def update(self):
#Importing the images. They are named a1.gif, a2.gif...a7.gif
frame=[]
for i in range(1,10):
fname="CORE\\a"+str(i)+".gif"
frame+=[PhotoImage(file=fname)]
def do_animation(currentframe):
def do_image():
self.wrap.create_image(100,70,image=frame[currentframe], tag='ani')
# Delete the current picture if one exists
self.wrap.delete('ani')
try:
do_image()
except IndexError:
# End of image list reached, start over at the first image
#- works for an arbitrary number of images
currentframe = 0
do_image()
self.wrap.update_idletasks() #Force redraw
currentframe = currentframe + 1
# Call myself again to keep the animation running in a loop
root.after(self.scale.get(), do_animation, currentframe)
# Start the animation loop just after the Tkinter loop begins
root.after(100, do_animation, 0)
app = App(root)
#app.geometry("800x480")
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()