How to stop/pause and restart the code using same button? (tkinter) - python

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

Related

tkinter - loading gif until def has came out

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

tkinter button function calls: multiple presses calling same function multiple times

I'm trying to make a Raspberry Pi Media Player, using omxplayer and tkinter.
I grab upto 16 video from a USB drive(or in local folder) and display them as thumbnails on a grid of buttons. When a button is pressed, it plays that video with omxplayer in fullscreen(at which point you cant access the tkinter window anymore until the video ends). Basically I want to enable selection of video only when there isn't a video playing already.
The problem I'm facing is with multiple presses on one button, or presses on other buttons before the video gets to play. This causes all the videos to queue up and immediately play one after another. I don't want subsequent presses to register until the first video (played from the first button press) is done playing. I try to set a boolean variable video_is_playing, and check its state in the function call, but it never enters the else case.
I tried to disable all buttons after you press any of them, then enabling them all after the video is done playing, I tried to use a variable to check if a video is done playing, and pretty much anything else I could think of doing to prevent multiple button presses.
Here is some parts of my code (sorry if it seems long, but I think everything included is relevant):
class TkinterGUI:
def __init__(self):
self.folder_name="videos"
self.vid_path = f"{os.path.split(os.getcwd())[0]}/{os.path.split(os.getcwd())[1]}/{self.folder_name}/"
self.videos = []
self.video_is_playing = False
self.vidbuttons = []
for f in os.listdir(f"{self.vid_path}"):
if '.mp4' in f:
self.videos.append(f)
self.videos.sort()
self.videos_iterator = iter(self.videos)
def pack_thumbnail(self, path, button):
#putting video thumbnail in button with imageio
pass
def disable_buttons(self, window):
for b in self.vidbuttons:
b.config(state=tk.DISABLED)
window.update()
print(">>all buttons diabled")
def enable_buttons(self, window):
for b in self.vidbuttons:
b.config(state=tk.NORMAL)
window.update()
print(">>all buttons enabled")
def play_vid(self, i, j, window):
try:
self.disable_buttons(window)
if self.video_is_playing == False:
self.video_is_playing=True
k = (i*4)+j
video = self.videos[k]
path = f"{self.vid_path}/{video}"
print(f">>now playing: {video} of duration {self.vid_duration(path)}")
omxp = Popen(['omxplayer', path])
omxp.wait()
print(f"video {video} is done playing!")
else:
print("a video seems to be playing already")
return
except Exception as e:
print(e)
finally:
self.video_is_playing = False
self.enable_buttons(window)
def video_player_window(self):
window = tk.Tk()
window.attributes("-fullscreen", True)
#left side frame(blank for now)
frame1 = tk.Frame(master=window, width=200, height=100, bg="white")
frame1.pack(fill=tk.Y, side=tk.LEFT)
#main video player frame(contains 4x4 grid of video thumbnails)
frame2 = tk.Frame()
for i in range(4):
frame2.columnconfigure(i, weight=1, minsize=75)
frame2.rowconfigure(i, weight=1, minsize=50)
for j in range(4):
frame = tk.Frame(master=frame2, relief=tk.FLAT, borderwidth=1)
frame.grid(row=i, column=j, padx=5, pady=5)
vid=next(self.videos_iterator, "end")
print(vid)
if vid != "end":
button = tk.Button(master=frame, highlightcolor="black", text=f"Row {i}\nColumn {j}", command= partial(self.play_vid, i, j, window))
self.pack_thumbnail(self.vid_path+f"{vid}", button)
button.pack(padx=5, pady=5)
self.vidbuttons.append(button)
else:
img = Image.open(f"vidnotfound.png")
img = img.resize((424, 224))
image = ImageTk.PhotoImage(img)
label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}", image=image)#, compound='center')
label.image = image
label.pack(padx=5, pady=5)
frame2.pack()
window.bind("<Escape>", lambda x: window.destroy())
window.mainloop()
tkin = TkinterGUI()
tkin.video_player_window()
I used functools.partial() to pass i, j indices to the play_vid function, so I can use those indices to know which video from the list to play. Here is everything I imported:
import tkinter as tk
import imageio
from PIL import ImageTk, Image
from pathlib import Path
from functools import partial
import subprocess
from subprocess import Popen
on a side note: is there a better way to accomplish what I want to do with the button grid? I'd like each button to call the same function but play different videos, is there any attribute I could use or anything?

How to update tkinter label text in real time

I have an application that gets the css3 colour of the pixel your cursor is on, and I would like to use tkinter to display the text in a little window. I following is the tkinter part of my code:
import pyautogui, PIL
import tkinter as tk
def cursorpixel():
x,y = pyautogui.position()
pixel = (x,y,x+1,y+1)
return pixel
def grabColor(square, max_colors=256):
img=PIL.ImageGrab.grab(square)
color = img.getcolors(max_colors)
return color
def main():
root=tk.Tk()
root.minsize(150, 50)
color = tk.Label(root,
text= grabColor(cursorpixel()),
fg = "black",
font = "Arial").pack()
root.mainloop()
while __name__ == "__main__":
main()
This works as I want, without the function of updating the label text whenever my cursor moves across the screen. It works once when launching the application and the label text stays the same. How would I make it so the label text updates whenever my cursor moves? I am using python 3.7
Thank you
Assigning a variable to the text argument doesn't help, because even if the value of the variable changes, it will not be reflected in the label. Here is my approach to this (this is just one out of many possible ways)
import pyautogui, PIL
import tkinter as tk
from threading import Thread
def cursorpixel():
x,y = pyautogui.position()
pixel = (x,y,x+1,y+1)
grabColor(pixel)
def grabColor(square, max_colors=256):
global color_label,root
img=PIL.ImageGrab.grab(square)
color = img.getcolors(max_colors)
color_label.config(text=color)
def refresh():
while True:
cursorpixel()
def main():
global color_label,root
root=tk.Tk()
root.minsize(150, 50)
color_label = tk.Label(root,
fg = "black",
font = "Arial")
color_label.pack()
Thread(target=refresh).start()
root.mainloop()
if __name__ == "__main__":
main()
NOTES
I have used multi threading instead and created a function refresh() which triggers the cursorpixel() in an infinite loop.
I have called the grabColor() function from cursorpixel() having pixel as parameter.
I have used the color_label.config() method to change the text in the label, you could also use color_label['text'] or maybe assign a textvariable var = StringVar() to the label and then use var.set() on it.
I am not sure if it is a good choice to put the __name__='__main__' in a while loop as you will not be able to close the window without terminating the task, new one will pop up every time you try to do so.
Answer
I added the .after command into the grabColor() function and combined the cursorpixel and grabColor() functions. I used .config to update the color. Here is the code:
import pyautogui, PIL
from tkinter import *
root=Tk()
root.minsize(150, 50)
colortext = Label(root)
colortext.pack()
def grabColor():
x,y = pyautogui.position()
pixel = (x,y,x+1,y+1)
img=PIL.ImageGrab.grab(bbox=pixel)
color = img.getcolors()
colortext.config(text=str(color))
root.after(100, grabColor)
grabColor()
root.mainloop()
Sources / Additional Resources
How do I create an automatically updating GUI using Tkinter?
You can use after() to grab the color periodically:
import tkinter as tk
from PIL import ImageGrab
def grab_color(label):
x, y = label.winfo_pointerxy()
color = ImageGrab.grab((x, y, x+1, y+1)).getpixel((0, 0))
label.config(text=str(color))
label.after(100, grab_color, label)
def main():
root = tk.Tk()
color_label = tk.Label(root, width=20)
color_label.pack(padx=10, pady=10)
grab_color(color_label)
root.mainloop()
if __name__ == "__main__":
main()
Note that winfo_pointerxy() is used instead of pyautogui.position() to reduce dependency on external module.

I don't know why this simple piece of python code isn't running

My idea in this code is running an app with Tkinter that 'lights on" a Seven Segment Display depending on which number I press on my keyboard.
import tkinter as tk
import keyboard
import time
from PIL import ImageTk, Image
def main():
window = tk.Tk()
window.title("AutoSegment")
window.geometry("459x767")
path=r"C:\Users\The Man Himself\Desktop\SSG\welcome.jpg"
img = ImageTk.PhotoImage(Image.open(path))
panel = tk.Label(window, image = img).pack(side = "bottom", fill = "both", expand = "yes")
listener()
tk.mainloop()
def set(name):
path=r"C:\Users\The Man Himself\Desktop\SSG\%s.jpg" %name
img = ImageTk.PhotoImage(Image.open(path))
panel = tk.Label(window, image = img).pack(side = "bottom", fill = "both", expand = "yes")
listener()
tk.mainloop()
def listener():
while True:
try:
if keyboard.is_pressed('1'):
set("1")
break
elif keyboard.is_pressed('2'):
set("2")
break
elif keyboard.is_pressed('3'):
set("3")
break
elif keyboard.is_pressed('4'):
set("4")
break
elif keyboard.is_pressed('5'):
set("5")
break
elif keyboard.is_pressed('6'):
set("6")
break
elif keyboard.is_pressed('7'):
set("7")
break
elif keyboard.is_pressed('8'):
set("8")
break
elif keyboard.is_pressed('9'):
set("9")
break
elif keyboard.is_pressed('0'):
set("0")
break
except:
set("error")
main()
I have not worked with the keyboard module, but I can show you how to work without it.
A couple of things; window is created inside a function which means that the name window is local to that function. Instead create the window in the global scope. Also the function set() is a builtin function and if you redefine it you will not be able to access the builtin function. I have called it set_display() instead.
As you will change the image in panel it's better to create it in the global namespace. Also, to be able to change it you must keep a reference, i.e. give it the name panel and then pack it. Otherwise the name panel will point to the return value from pack() which is = None.
When you later change the image in the label in the function set_display() you must also save a reference to the image in the label, explicitly commented in my example code.
Then I use bind() to hook the keyboard which is a standard method in tkinter widgets. After that I start mainloop() which waits until a key is pressed and then calls keypress().
import tkinter as tk
from PIL import ImageTk, Image
def set_display(name):
path = r"C:\Users\The Man Himself\Desktop\SSG\%s.jpg" %name
img = ImageTk.PhotoImage(Image.open(path))
panel.config(image=img) # Load new image into label
panel.image = img # Save reference to image
def keypress(event):
if event.char == '': # Shift, ctrl etc, returns empty char
set_display('error')
elif event.char in '1234567890': # Hook all numbers
set_display(event.char)
else:
set_display('error')
window = tk.Tk()
window.title("AutoSegment")
window.geometry("459x767")
# Create Seven Segment Display label in global namespace
path = r"C:\Users\The Man Himself\Desktop\SSG\welcome.jpg"
img = ImageTk.PhotoImage(Image.open(path))
panel = tk.Label(window, image=img)
panel.pack(side="bottom", fill="both", expand="yes")
window.bind('<KeyPress>', keypress)
window.mainloop()

Exit Tks mainloop in Python?

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)

Categories