how can I exit a tkinter window after changing images? - python

So I'm trying to make a little battle scene using Tkinter, the code is supposed to change the image, wait a couple of seconds, then exit the Tkinter window. The code I have just makes a little pause when the button to change images is pressed. I'm still a beginner and some concepts are hard for me to grasp.
Here is the code:
from tkinter import *
import time
class MainWindow():
def __init__(self, main):
# canvas for image
self.canvas = Canvas(main, width=660, height=440)
self.canvas.grid(row=0, column=0)
# images
self.my_images = []
self.my_images.append(PhotoImage(file = "att1.gif"))
self.my_images.append(PhotoImage(file = "att2.gif"))
self.my_image_number = 0
# set first image on canvas
self.image_on_canvas = self.canvas.create_image(0, 0, anchor = NW, image = self.my_images[self.my_image_number])
# button to change image
self.button = Button(main, text="FIGHT", command=self.onButton)
self.button.grid(row=1, column=0)
#----------------
def onButton(self):
# next image
self.my_image_number = 1
if self.my_image_number == 1:
time.sleep(2)
root.quit()
# change image
self.canvas.itemconfig(self.image_on_canvas, image = self.my_images[self.my_image_number])
root = Tk()
MainWindow(root)
root.mainloop()
some of this code is borrowed, I tried to alter it to fit my purpose

The image is not changed because time.sleep(2) blocks tkinter update. After the sleep, tkinter quit and so the image is not updated.
Since you have only 2 images and you want to exit the tkinter window 2 seconds after the change of image, try:
def onButton(self):
self.canvas.itemconfig(self.image_on_canvas, image=self.my_images[1])
root.after(2000, root.destroy) # close root window after 2 seconds

Related

How to change tkinter label background image on button click

img= (Image.open("image/frame.png")).resize((240, 240), Image.ANTIALIAS)
new_image = ImageTk.PhotoImage(img)
panel = tk.Label(right_workspace, image=new_image)
panel.pack(side = "top", fill = "none", expand = "none", pady=29)
Thats my label with its image background. Now how can I change this background by a function so everytime my program generates a new qrcode from an input it replaces previous background?
I gather what you're doing, but please leave a minimum reproducible code in future.
Here's an example of how you'd create a functioning tkinter GUI that displays a random image from a list of QR .pngs in a set folder when the button is pressed. You should be able to adapt this to the other part of your program that generates the QR code for you.
import tkinter as tk
from PIL import Image, ImageTk
import os
import random
class QrGenerator:
def __init__(self, main):
self.main = main
self.panel = tk.Label(main)
self.panel.grid(row=0, column=0)
self.button = tk.Button(main, text='Random QR', command=self.random_qr)
self.button.grid(row=1, column=0)
def random_qr(self):
fp = r'C:\Filepath\QR Codes Folder'
os.chdir(fp)
qr_code = random.choice(os.listdir(fp))
print(qr_code)
img = Image.open(qr_code).resize((240, 240), Image.ANTIALIAS)
new_image = ImageTk.PhotoImage(img)
self.panel.configure(image=new_image)
self.panel.image = new_image
if __name__ == '__main__':
root = tk.Tk()
gui = QrGenerator(root)
root.mainloop()

Loading GUI Python script on boot for raspberry pi

I'm on Raspberry Pi 3B+ (Raspbian) and I'm trying to load a Python script on boot which display 720p images into full screen mode, the user should then be able to press a push button to go to the next image.
Currently, I have created the code which allows for the images to display and the images to change due to the push button.
Right now I'm having trouble with having the images load into full screen mode and boot on start-up. I have followed many guides and tutorials on how I can load a GUI Python script on boot but none of them have seemed to work for my specific script (but they loaded on boot successfully from their example scripts) which is making me think that there's an issue in the code for why it won't boot (probably because I haven't implemented so that they open in full screen). These are all the guides which I tried out (https://www.raspberrypi-spy.co.uk/2015/02/how-to-autorun-a-python-script-on-raspberry-pi-boot/ , raspberry pi : Auto run GUI on boot , https://raspberrypi.stackexchange.com/questions/4123/running-a-python-script-at-startup , https://www.digikey.ca/en/maker/blogs/2018/how-to-run-python-programs-on-a-raspberry-pi , https://www.instructables.com/Raspberry-Pi-Launch-Python-script-on-startup/ , https://bc-robotics.com/tutorials/running-python-program-boot-raspberry-pi/) and I'd like to focus on the GUI method from this tutorial for my question (https://learn.sparkfun.com/tutorials/how-to-run-a-raspberry-pi-program-on-startup/all )
I'm very much a beginner right now when it comes to GUI programming (I took the original script from online and modified it to include the push button) and I'm not too sure on how I can implement the full screen option into the code which displays the images.
Here is what I have currently (will load images into a window, you can then change to the next image using a push button). At the beginning of my code, I tried adding in some functions from the example listed later which allowed toggling of full screen and also their associated root functions at the bottom to complement this
import tkinter as tk
from PIL import Image, ImageTk
import time
import sys
import os
import keyboard
import RPi.GPIO as GPIO
import time
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(10, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.add_event_detect(10, GPIO.FALLING)
def toggle_fullscreen(event=None): # I added this in to try full screen
global root
global fullscreen
# Toggle between fullscreen and windowed modes
fullscreen = not fullscreen
root.attributes('-fullscreen', fullscreen)
resize()
# Return to windowed mode
def end_fullscreen(event=None): # I added this in to try full screen
global root
global fullscreen
# Turn off fullscreen mode
fullscreen = False
root.attributes('-fullscreen', False)
root= tk.Tk() # I added this in to try full screen
class HiddenRoot(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
#hackish way, essentially makes root window
#as small as possible but still "focused"
#enabling us to use the binding on <esc>
self.wm_geometry("0x0+0+0")
self.window = MySlideShow(self)
#self.window.changeImage()
self.window.startSlideShow()
class MySlideShow(tk.Toplevel):
def __init__(self, *args, **kwargs):
tk.Toplevel.__init__(self, *args, **kwargs)
#remove window decorations
self.overrideredirect(True)
#save reference to photo so that garbage collection
#does not clear image variable in show_image()
self.persistent_image = None
self.imageList = []
self.pixNum = 0
#used to display as background image
self.label = tk.Label(self)
self.label.pack(side="top", fill="both", expand=True)
self.getImages()
#### Added the GPIO event here
GPIO.add_event_callback(10, self.changeImage)
def getImages(self):
'''
Get image directory from command line or use current directory
'''
if len(sys.argv) == 2:
curr_dir = sys.argv[1]
else:
curr_dir = '.'
for root, dirs, files in os.walk(curr_dir):
for f in files:
if f.endswith(".png") or f.endswith(".jpg"):
img_path = os.path.join(root, f)
print(img_path)
self.imageList.append(img_path)
def startSlideShow(self, delay=4):
myimage = self.imageList[self.pixNum]
self.pixNum = (self.pixNum + 1) % len(self.imageList)
self.showImage(myimage)
#self.after(delay*1000, self.startSlideShow)
# while True:
# buttonState = GPIO.input(buttonPin)
# if buttonState == False:
# self.startSlideShow(self, delay=4)
##staticmethod
def changeImage(self, pull_up_down=GPIO.PUD_DOWN):
prev_input = 0
while True:
#take a reading
input = GPIO.input(10)
#if the last reading was low and this one high, print
if ((not prev_input) and input):
myimage = self.imageList[self.pixNum]
self.pixNum = (self.pixNum + 1) % len(self.imageList)
self.showImage(myimage)
#update previous input
prev_input = input
#slight pause to debounce
time.sleep(0.05)
# myimage = self.imageList[self.pixNum]
# self.pixNum = (self.pixNum + 1) % len(self.imageList)
# self.showImage(myimage)
def showImage(self, filename):
image = Image.open(filename)
img_w, img_h = image.size
scr_w, scr_h = self.winfo_screenwidth(), self.winfo_screenheight()
width, height = min(scr_w, img_w), min(scr_h, img_h)
image.thumbnail((width, height), Image.ANTIALIAS)
#set window size after scaling the original image up/down to fit screen
#removes the border on the image
scaled_w, scaled_h = image.size
self.wm_geometry("{}x{}+{}+{}".format(scaled_w,scaled_h,0,0))
# create new image
self.persistent_image = ImageTk.PhotoImage(image)
self.label.configure(image=self.persistent_image)
slideShow = HiddenRoot()
slideShow.bind("<Escape>", lambda e: slideShow.destroy()) # exit on esc
slideShow.mainloop()
root.bind('<F11>', toggle_fullscreen) # I added this in to try full screen
root.bind('<Escape>', end_fullscreen) # I added this in to try full screen
toggle_fullscreen() # I added this in to try full screen
root.mainloop() # I added this in to try full screen
GPIO.cleanup()
And from that guide I said to note earlier, here is the code they which loads a clock into fullscreen mode (and I followed their method of loading the script on boot which worked)
import tkinter as tk
import tkinter.font as tkFont
import time
###############################################################################
# Parameters and global variables
# Default font size
font_size = -24
# Declare global variables
root = None
dfont = None
frame = None
dtime = None
# Global variable to remember if we are fullscreen or windowed
fullscreen = False
###############################################################################
# Functions
# Toggle fullscreen
def toggle_fullscreen(event=None):
global root
global fullscreen
# Toggle between fullscreen and windowed modes
fullscreen = not fullscreen
root.attributes('-fullscreen', fullscreen)
resize()
# Return to windowed mode
def end_fullscreen(event=None):
global root
global fullscreen
# Turn off fullscreen mode
fullscreen = False
root.attributes('-fullscreen', False)
resize()
# Automatically resize font size based on window size
def resize(event=None):
global time_dfont
global button_dfont
global frame
# Resize font based on frame height (minimum size of 12)
# Use negative number for "pixels" instead of "points"
new_size = -max(12, int((frame.winfo_height() / 2)))
time_dfont.configure(size=new_size)
new_size = -max(12, int((frame.winfo_height() / 30)))
button_dfont.configure(size=new_size)
# Read values from the sensors at regular intervals
def update():
global root
global dtime
# Get local time
local_time = time.localtime()
# Convert time to 12 hour clock
hours = local_time.tm_hour
if hours > 12:
hours -= 12
# Add leading 0s
shours = str(hours)
smin = str(local_time.tm_min)
if hours < 10:
shours = '0' + shours
if local_time.tm_min < 10:
smin = '0' + smin
# Construct string out of time
dtime.set(shours + ':' + smin)
# Schedule the poll() function for another 500 ms from now
root.after(500, update)
###############################################################################
# Main script
# Create the main window
root = tk.Tk()
root.title("My Clock")
# Create the main container
frame = tk.Frame(root, bg='black')
# Lay out the main container (expand to fit window)
frame.pack(fill=tk.BOTH, expand=1)
# Variables for holding temperature and light data
dtime = tk.StringVar()
# Create dynamic font for text
time_dfont = tkFont.Font(family='Courier New', size=font_size)
button_dfont = tkFont.Font(size=font_size)
# Create widgets
label_time = tk.Label( frame,
textvariable=dtime,
font=time_dfont,
fg='red',
bg='black')
button_quit = tk.Button(frame,
text="Quit",
font=button_dfont,
command=root.destroy,
borderwidth=0,
highlightthickness=0,
fg='gray10',
bg='black')
# Lay out widgets in a grid in the frame
label_time.grid(row=0, column=0, padx=20, pady=20)
button_quit.grid(row=1, column=0, padx=5, pady=5, sticky=tk.E)
# Make it so that the grid cells expand out to fill window
frame.rowconfigure(0, weight=10)
frame.rowconfigure(1, weight=1)
frame.columnconfigure(0, weight=1)
# Bind F11 to toggle fullscreen and ESC to end fullscreen
root.bind('<F11>', toggle_fullscreen)
root.bind('<Escape>', end_fullscreen)
# Have the resize() function be called every time the window is resized
root.bind('<Configure>', resize)
# Schedule the poll() function to be called periodically
root.after(20, update)
# Start in fullscreen mode and run
toggle_fullscreen()
root.mainloop()
Now what I'm trying to figure out is how I can implement their method of going into a full screen display into my program
Thank you :)!
I am not sure if I really understood your question.
I understood you want to change on certain events to full screen. If yes, you just need to change the geometry property of your root. Like for example
master.geometry("{0}x{1}+0+0".format(
master.winfo_screenwidth()-pad, master.winfo_screenheight()-pad))
above code checks for screen size and reduces some distance on w and h to fit in screen. Have a look to below link.
https://stackoverflow.com/questions/7966119/display-fullscreen-mode-on-tkinter#:~:text=This%20creates%20a%20fullscreen%20window,geometry%20and%20the%20previous%20geometry.&text=You%20can%20use%20wm_attributes%20instead%20of%20attributes%20%2C%20too.
you can create a function with that call and trigger it on start up or button pressed.
Hope my guess is right and this answers helps.
Cheers

Is there a way to implement a time delay on a single image?

A small program that I'm working on using python is intended to display a series of images and audio clips in a certain order. I'm able to display two of the images in the same window, one overlaying the other as intended, but I'm looking for a way to make the second image appear after a certain amount of time (30 seconds) instead of as soon as the program is run. I cannot seem to get the second image called textBox.jpg to run separately from the rest of the program, though.
To try to resolve this problem, I put in the sleep() function, but that just delays the entire code from starting.
from tkinter import *
from tkinter import ttk
from PIL import Image, ImageTk
import time
# Title/Main
window = Tk()
window.title("Just Monika")
window.configure(background="black")
# control window size to make full-screen
width_value=window.winfo_screenwidth()
height_value=window.winfo_screenheight()
window.geometry("%dx%d+0+0" % (width_value, height_value))
# Monika photo
# this block will allow the image to resize to fit the window's size
# indefinitely
def resize_image(event):
new_width = event.width
new_height = event.height
image = copy_of_image.resize((new_width, new_height))
photo = ImageTk.PhotoImage(image)
label.config(image = photo)
label.image = photo #avoid garbage collection
monika = Image.open("C:\\Python Programs\\Just Monika\\Images\\justMonika.png")
copy_of_image = monika.copy()
photo = ImageTk.PhotoImage(monika)
label = ttk.Label(window, image = photo)
label.bind('<Configure>', resize_image)
label.pack(fill=BOTH, expand=YES)
# text box image
textBox = Image.open("C:\\Python Programs\\Just Monika\\Images\\textBox.jpg")
tkTextBox = ImageTk.PhotoImage(textBox)
panel1 = Label(window, image = tkTextBox)
panel1.place(x=675, y=300)
time.sleep(30)
I want the second image, textBox.jpg, to be called after the program has been running for about 30 seconds and the other image has already been displayed.
As #Masoud suggested, you can use the universal widget after() method to schedule a function to be called after a specified time delay:
from tkinter import *
from tkinter import ttk
from PIL import Image, ImageTk
PATH_IMAGE1 = 'justMonika.png'
PATH_IMAGE2 = 'textBox.png'
DELAY = 5000 # In milliseconds
# Title/Main
window = Tk()
window.title("Just Monika")
window.configure(background="black")
# control window size to make full-screen
width_value=window.winfo_screenwidth()
height_value=window.winfo_screenheight()
window.geometry("%dx%d+0+0" % (width_value, height_value))
# Monika photo
# this block will allow the image to resize to fit the window's size
# indefinitely
def resize_image(event):
new_width = event.width
new_height = event.height
image = copy_of_image.resize((new_width, new_height))
photo = ImageTk.PhotoImage(image)
label.config(image = photo)
label.image = photo #avoid garbage collection
def display_2nd_image():
global panel1
# text box image
textBox = Image.open(PATH_IMAGE2)
tkTextBox = ImageTk.PhotoImage(textBox)
panel1 = Label(window, image=tkTextBox)
panel1.place(x=675, y=300)
panel1.image = tkTextBox #avoid garbage collection
monika = Image.open(PATH_IMAGE1)
copy_of_image = monika.copy()
photo = ImageTk.PhotoImage(monika)
label = ttk.Label(window, image=photo)
label.bind('<Configure>', resize_image)
label.pack(fill=BOTH, expand=YES)
window.after(DELAY, display_2nd_image)
window.mainloop()

Rapidly Display Images With Tkinter

I am looking for an efficient way to rapidly display images with tkinter, and I mean like really fast. Currently I have this code:
from tkinter import*
import threading
import time
root = Tk()
root.geometry("200x200")
root.title("testing")
def img1():
threading.Timer(0.2, img1).start()
whitei = PhotoImage(file="white.gif")
white = Label(root, image=whitei)
white.image = whitei
white.place(x=0, y=0)
def img2():
threading.Timer(0.2, img2).start()
blacki = PhotoImage(file="black.gif")
black = Label(root, image=blacki)
black.image = blacki
black.place(x=0, y=0)
img1()
time.sleep(0.1)
img2()
root.mainloop()
Essentially the code just displays a black and white image but it puts my CPU at 100% usage and is pretty slow no matter how small I make the amount of time each picture is displayed for. Is there a faster, more efficient way to do this?
As mentioned, I would suggest using after. You aren't really supposed to alter any tkinter objects outside your main thread. Also, creating a new object each time isn't the most efficient. Here's something I would try:
import tkinter as tk
root = tk.Tk()
root.geometry("200x200")
root.title("testing")
whitei = tk.PhotoImage(file="white_.gif")
blacki = tk.PhotoImage(file="black_.gif")
label = tk.Label(root, image=whitei)
label.image1 = whitei
label.image2 = blacki
label.place(x=0, y=0)
time_interval = 50
def img1():
root.after(time_interval, img2)
label.configure(image=whitei)
def img2():
root.after(time_interval, img1)
label.configure(image=blacki)
root.after(time_interval, img1)
root.mainloop()
You do not need to use threading. 2nd unless you are using sleep() inside of a separate thread you should never use sleep in a tkinter application. sleep() interrupts the mainloop and will cause tkinter to freeze up until sleep is finished. This is 99.9% of the time not what you want to do so here you should use after() for any timed events.
You can simple create each label for each image and then with a tracking variable raise the correct label to the top.
Here is a simple example.
from tkinter import *
root = Tk()
root.geometry("200x200")
root.title("testing")
current_image = ""
black_image = PhotoImage(file="black.gif")
white_image = PhotoImage(file="white.gif")
black_label = Label(root, image=black_image)
white_label = Label(root, image=white_image)
black_label.image = black_image
white_label.image = white_image
black_label.grid(row=0, column=0)
white_label.grid(row=0, column=0)
def loop_images():
global current_image, black_image, white_image
if current_image == "white":
black_label.tkraise(white_label)
current_image = "black"
else:
white_label.tkraise(black_label)
current_image = "white"
root.after(100, loop_images)
loop_images()
root.mainloop()

tkinter: move images with background

I'm looking for a way to move multiple images together with the background. Moving the background image works fine, but I can't figure out how to add two images on top (they disappear immediately) and then move together with the background. I guess there is an easy way to do that?
I appreciate any hint!
from tkinter import *
import time
tk = Tk()
w = 1000
h = 800
pos = 0
canvas = Canvas(tk, width=w, height=h)
canvas.pack()
tk.update()
background_image = PhotoImage(file="bg.gif")
background_label = Label(tk, image=background_image)
background_label.place(x=0, y=0)
tk.update()
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(tk, image=image)
label.place(x=files[f][0],y=files[f][1])
tk.update()
def move(xPos):
pos = background_label.winfo_x() - xPos
while background_label.winfo_x() > pos:
background_label.place(x=background_label.winfo_x()-25)
tk.update()
time.sleep(0.001)
img = {"file1.gif": [10,10], "file2.gif": [50,50]}
addImages(img)
move(100)
tk.mainloop()
I'm having difficulty in understanding your code. Why create a canvas and then not use it? You have also littered your code with tk.update(), most of which are unnecessary. But, the described problem is because you create the labels inside a function and the association between label and image gets garbage collected when the function exits. You have to explicitly remember this association:
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(tk, image=image)
label.image = image # Lets the label remember the image outside the function
label.place(x=files[f][0],y=files[f][1])
If you are then going to move these labels you might want to keep some kind of reference to them or you won't be able to address them.
Complete example
I changed tk to root because tk is the name usually used as alias for tkinter (eg. import tkinter as tk) which gets confusing.
I'm creating a image_list to hold references to the labels containing images. Later I use the list to loop through the labels and move them.
After I have built the GUI I wait 1000 milliseconds before starting the move function. Also I move the images just 1 pixel at a time to clearer see the action.
from tkinter import *
import time
root = Tk()
root.geometry('800x600') # Setting window size instead of usin canvas to do that
pos = 0
background_image = PhotoImage(file="bg.gif")
background_label = Label(root, image=background_image)
background_label.place(x=0, y=0)
image_list = [] # List for holding references to labels with images
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(root, image=image)
label.image = image # Remember the image outside the function
label.place(x=files[f][0],y=files[f][1])
image_list.append(label) # Append created Label to the list
def move(xPos):
pos = background_label.winfo_x() - xPos
while background_label.winfo_x() > pos:
background_label.place(x=background_label.winfo_x()-1)
for image in image_list: # Loop over labels in list
image.place(x=image.winfo_x()-1) # Move the label
root.update()
time.sleep(0.001)
img = {"file1.gif": [10,10], "file2.gif": [50,50]}
addImages(img)
root.after(1000, move, 100) # Waits 1 second and then moves images
root.mainloop()
By the way; after is a function much preferred over sleep as sleep suspends the program until its finished, whereas after works by making a call after a time and the program runs meanwhile. But if you are ok with that the program freezes during the move, then why not.

Categories

Resources