Update canvas image on button click event - python-3.x

I'm trying to modify the displayed image on a canvas when a button is clicked on.
I have an object "Window" which will contain my window layers. this object contain a canvas named "draw_frame" and a button named "source_button". I add the command "self.load_static_source" to my button but when i click on my button nothing happen.
(Not exactely nothing because i tried to add default background in the init scope and when i click on my button after that the image on the canvas just diseappeared and the new selected image wasn't draw).
Here is my code:
from tkinter import *
from tkinter import filedialog
from PIL import Image, ImageTk
DARK_THEME = "grey"
LIGHT_THEME = "white"
THEME = LIGHT_THEME
class Window():
# object constructor
def __init__(self, root, theme):
# Sections #
self.toolbar_frame = LabelFrame(root, bg=theme, height="40")
self.toolbar_frame.pack(side=TOP, fill=X)
# Canvas #
self.draw_frame = Canvas(root)
self.draw_frame.pack(side=RIGHT, fill=BOTH, expand=True)
self.frame = self.draw_frame.create_image(0, 0, anchor=NW)
# Buttons #
self.source_button = Button(self.toolbar_frame, text="Source", bg=theme, command= lambda: self.load_static_source("./Sources/"))
self.source_button.pack(side=LEFT)
# Load image with tk compatibility
def load_image(self, path_):
print(path_) ### DEBUG ###
image = Image.open(path_)
return ImageTk.PhotoImage(image)
# Change canvas source with static one
def load_static_source(self, dir_):
path_ = filedialog.askopenfilename(initialdir = dir_, title = "Select file", filetypes = (("jpeg files","*.jpg"),("all files","*.*")))
self.draw_frame.itemconfig(self.frame, image=self.load_image(path_))
root = Tk()
Window(root, THEME)
root.mainloop()
I found an other post talking about that and i can't find difference between what i did and the given solution and that's why i don't understand why that code isn't working.
Here are the exemple i found and the related post:
from Tkinter import *
#----------------------------------------------------------------------
class MainWindow():
#----------------
def __init__(self, main):
# canvas for image
self.canvas = Canvas(main, width=60, height=60)
self.canvas.grid(row=0, column=0)
# images
self.my_images = []
self.my_images.append(PhotoImage(file = "ball1.gif"))
self.my_images.append(PhotoImage(file = "ball2.gif"))
self.my_images.append(PhotoImage(file = "ball3.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="Change", command=self.onButton)
self.button.grid(row=1, column=0)
#----------------
def onButton(self):
# next image
self.my_image_number += 1
# return to first image
if self.my_image_number == len(self.my_images):
self.my_image_number = 0
# change image
self.canvas.itemconfig(self.image_on_canvas, image = self.my_images[self.my_image_number])
#----------------------------------------------------------------------
root = Tk()
MainWindow(root)
root.mainloop()
Related post : stackoverflow topic

You need to keep a reference to the image. Here's a link to the effbot page describing it: https://effbot.org/tkinterbook/photoimage.htm
You must keep a reference to the image object in your Python program, either by storing it in a global variable, or by attaching it to another object.
The solution Xeyes wrote is right, and this page explains.

So i found the solution. A bit weird but it works. I have to save the image in a class attribute before i give it to the canvas itemconfig method.
It now looks like :
self.placeholder = self.load_image(path_)
self.draw_frame.itemconfig(self.frame, image=self.placeholder)
Instead of just :
self.draw_frame.itemconfig(self.frame, image=self.load_image(path_))

Related

I am using tkinter ,I want to clear the canvas using a button(close_button) but the camera is always on and the button not doing anything

I am using Tkinter, I want to clear the canvas using a button(close_button) but the camera is always on and the button not doing anything I want the button to close the camera feed and reset the canvas so that the canvas becomes as it was before I opened the cam
from tkinter import*
import tkinter as tk
from PIL import Image, ImageTk
import cv2
# new window function which will be called when button pressed
class OpenCam():
def __init__(self, window, cap):
self.window = window
self.cap = cap
self.width = self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)
self.height = self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
self.interval = 20 # Interval in ms to get the latest frame
# Create canvas for image
self.canvas = tk.Canvas(self.window, width=self.width, height=self.height)
self.canvas.place(x=100,y=100)
# close Button need to close cam and reset canvas
close_button = tk.Button(root, text="close", bg='black', fg='white', command=self.canvas.delete() )
close_button.place(x=610,y=0)
# Update image on canvas
self.update_image()
def update_image(self):
# Get the latest frame and convert image format
self.image = cv2.cvtColor(self.cap.read()[1], cv2.COLOR_BGR2RGB) # to RGB
self.image = Image.fromarray(self.image) # to PIL format
self.image = ImageTk.PhotoImage(self.image) # to ImageTk format
# Update image
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.image)
# Repeat every 'interval' ms
self.window.after(self.interval, self.update_image)
def new_window():
OpenCam(root, cv2.VideoCapture(0))
#create original window
root = tk.Tk()
root.title("ADD CAM: ")
canvas = tk.Canvas(root, height=1000, width=1000)
canvas.pack()
#create button that will be placed
button = tk.Button(root, text="ADD CAM", bg='black', fg='white', command=new_window )
button.place(x=0,y=0)
root.mainloop()

how to add image in tkinter?

I can't add image (gif image) to tkinter window.
from tkinter import *
from tkinter import filedialog
from PIL import ImageTk, Image
root = Tk()
def open_image():
qr_select = filedialog.askopenfilename(title = "open")
im = PhotoImage(file=qr_select)
w1 = Label(window, image = im)
w1.image = im
w1.config(image=im)
w1.pack(side="right")
def window_function():
global window
window=Tk()
window.geometry("800x550+650+250")
window.title("QR_Scanner")
btn = Button(window,text = "open a gif picture",command = open_image)
btn.pack()
root.iconify()
window.mainloop()
btn = Button(root,text = "open window",command = window_function)
btn.pack()
root.mainloop()
my error is (_tkinter.TclError: image "pyimage1" doesn't exist)
The reason you can't see your gif in the window is that you haven't made a reference to the image so it is collected in Tkinters garbage collector. Read More About This Here. To Add a reference to the image you can do this:
w1.image = im
And add it in your code here:
def open_image():
qr_select = filedialog.askopenfilename(title = "open")
im = PhotoImage(file=qr_select)
w1 = Label(root, image = im)
w1.image = im #Keep A Reference To The Image
w1.config(image=im)
w1.pack(side="right")
The reason you are getting pyimage1 doesn't exist is because you have more than one instance the Tk and there is only meant to be 1. You have to make your window a Toplevel() by replacing: window=Tk() with window=TopLevel()

Skip files on button click [tkinter + Python 3.5]

Suppose I have 'n' image files in a directory filedir which I want to view via tkinter console. Now, I want to skip a few files whenever necessary, using a button click event which invokes a function nextFile().
E.g.
import os
def nextFile():
global img
img_2 = process(img)
return img_2
window = tk.Tk()
window.title("File Viewer")
files = os.listdir(filedir)
button1 = tk.Button(window, text="Browse Files...", fg="black", command=askopenfilename)
button2 = tk.Button(window, text="SELECT", width=50, command=nextFile)
canvas = tk.Canvas(window, width=width, height=height)
button1.pack(side=tk.LEFT)
button2.pack(side=tk.BOTTOM)
canvas.pack()
for f in files:
img = cv2.cvtColor(cv2.imread(filedir + '/' + f), cv2.COLOR_BGR2RGB)
photo = ImageTk.PhotoImage(image=Image.fromarray((img))
canvas.create_image(0, 0, image=photo, anchor=tk.CENTER)
window.mainloop()
Any help is appreciated. Thanks!
Here is a simple example using PIL to load the inital image and then use a button and function to load each next image.
import tkinter as tk
from PIL import ImageTk, Image
import os
path = 'C:/your/file/path/here'
def nextFile():
# the global is needed to keep track of current index and also keep
# a reference of the image being displayed so its is not garbage collected.
global current_ndex, image
current_ndex += 1
if current_ndex < len(files):
img = Image.open('{}/{}'.format(path, files[current_ndex]))
image = ImageTk.PhotoImage(img)
# clear the canvas before adding the new image.
canvas.delete("all")
canvas.create_image(0, 0, image=image, anchor=tk.CENTER)
my_window = tk.Tk()
my_window.title("File Viewer")
files = os.listdir(path)
current_ndex = 0
button2 = tk.Button(my_window, text="Next", width=50, command=nextFile)
canvas = tk.Canvas(my_window, width=100, height=100)
button2.pack(side=tk.BOTTOM)
canvas.pack()
first_image = files[0]
img = Image.open('{}/{}'.format(path, first_image))
image = ImageTk.PhotoImage(img)
canvas.create_image(0, 0, image=image, anchor=tk.CENTER)
my_window.mainloop()

Tkinter button doesn't work when I add an image on this button

I'm creating a button with tkinter but I want to had an image on this button. When I make it, the button doesn't work. Nonetheless, when I remove the image of my button, it works..
# Creating a photoimage object to use image
photo = PhotoImage(file = r"C:/Users/toto/Documents/toto/img/Flechedroiteverte.png")
# Resizing image to fit on button
photoimage = photo.subsample(1, 1)
PageSuivante=Button(main_win, text = 'Click Me !', image = photoimage)
PageSuivante.grid(pady = 10)
You have to keep a reference in a label, I have the same problem. You can do something like:
add = PhotoImage(file=r"C:/Users/toto/Documents/toto/img/Flechedroiteverte.png")
label = Label(image=add)
label.image = add # keep a reference!
PageSuivante=Button(main_win, text = 'Click Me !', image = add)
def ShowFramePrepaChantier(main_win,frame):
delete_frame(main_win)
frame.tkraise()
frame.place(x=0, y=0)
bouton_DownloadDPA = Button(frame, text='Valider mes DPA sélectionnées',padx = 200)
bouton_DownloadDPA.grid(row=1, column=0, sticky=SE)
bouton_DownloadDPA.bind('<Button-1>', select)
## Creating a photoimage object to use image
photo = PhotoImage(file = r"C:/Users/toto/Documents/toto/img/Flechedroiteverte.png")
# Resizing image to fit on button
photoimage = photo.subsample(10, 10)
# here, image option is used to
# set image on button
# compound option is used to align
# image on LEFT side of button
bouton = Button(frame, text = 'Click Me !', image = photoimage,
compound = LEFT)
bouton.grid(pady = 100)
if __name__ == "__main__":
main_win = Tk()
main_win.configure(bg='lavender')
main_win.title("Test")
main_win.geometry("680x400")
second_frame = Frame(main_win)
bouton_PrepaChantier = Button(main_win, text='Click to change frame', command=lambda:ShowFramePrepaChantier(main_win,second_frame), height=3, width=20)
bouton_PrepaChantier.grid()
main_win.mainloop()

How to enhance window size selection on a tkinter project including button-image as label?

I'm currently working on a little project on python-3.x including some tkinter ressources. My program is made to display on a screen a list of pictures included in a directory, each picture is put on a button that is a sixth of the original image, and if we click on it, it display the image on his original size on a new window. The original window is set by the amount of pictures i put in the columns (i can choose in the code) and i ve made a scrollbar because i have to work with a lot of pictures.
But here is my problem, it's works fine except that if i change the window size, like reduce it for example, the buttons don't follow, they just vanish behind the window, and with the scrollbar.
I'm not particularly good in python so i was wondering that maybe by doing like a threading we could get the window size in live and then if the window size is inferior/superior of our columns of buttons, we could resize it and change the amount of columns then reload the page, but i will have to work with multiple image so it will take a lot of time.
from tkinter import *
from tkinter.filedialog import *
from tkinter.messagebox import *
from PIL import Image, ImageTk
import tkinter as tk
import glob
import os
import cv2
import copy
import _thread
import time
folder = 'X:/users/Robin/data/dataset-valid/visu/*.jpg'
a=glob.glob(folder)
fic = "../data/list.txt"
fichObj=open(fic,"w")
p = []
for f in a:
fichObj.write(f+"\n")
fichObj.close()
class SuperPhoto(object):
def __init__(self, photo , image):
self.photo = photo
temp = cv2.resize(image, (int((self.photo.width())/6) , int((self.photo.height())/6)))
red = temp[:,:,2].copy()
blue = temp[:,:,0].copy()
temp[:,:,0] = red
temp[:,:,2] = blue
temp = Image.fromarray(temp)
self.miniature = ImageTk.PhotoImage(temp)
def agrandir(self):
Newfen=Toplevel()
Newfen.geometry("+60+60")
#self.photo.resize((500,500))
print(type(self.photo))
label = Label(Newfen, image=self.photo, width=self.photo.width(), height=self.photo.height())
label.image = self.photo # keep a reference!
label.pack()
if os.path.exists (fic): #os.path utile
count = len(open(fic).readlines())
print(count)
#lin = open(fic).readlines()
#print(lin)
class ScrollableCanvas(Frame):
def __init__(self, parent, *args, **kw):
Frame.__init__(self, parent, *args, **kw)
canvas=Canvas(self,bg='#FFFFFF',width=300,height=300,scrollregion=(0,0,500,500))
canvas.update_idletasks()
vbar=Scrollbar(self,orient=VERTICAL)
vbar.pack(side=RIGHT, fill=Y)
vbar.config(command=canvas.yview)
canvas.config(width=1200,height=700)
canvas.config(yscrollcommand=vbar.set)
canvas.pack(side=LEFT,expand=True,fill=BOTH)
# create a frame inside the canvas which will be scrolled with it
self.interior = interior = Frame(canvas)
interior_id = canvas.create_window(0, 0, window=interior, anchor=NW )
# track changes to the canvas and frame width and sync them,
# also updating the scrollbar
def _configure_interior(event):
# update the scrollbars to match the size of the inner frame
size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
canvas.config(scrollregion="0 0 %s %s" % size)
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the canvas's width to fit the inner frame
canvas.config(width=interior.winfo_reqwidth())
interior.bind('<Configure>', _configure_interior)
def _configure_canvas(event):
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the inner frame's width to fill the canvas
canvas.itemconfigure(interior_id, width=canvas.winfo_width())
canvas.bind('<Configure>', _configure_canvas)
class Main_frame(Frame):
# Init
def __init__(self, fenetre_principale=None):
Frame.__init__(self, fenetre_principale)
self.grid()
self.scrollable_canvas = ScrollableCanvas(self)
self.scrollable_canvas.grid(row=1,column=1)
nbCol = 4
for file in a:
image = Image.open(file)
photo = ImageTk.PhotoImage(image)
w = photo.width()
L.append(int(w/6))
#print(L)
sumL = int(sum(L)/nbCol)
print(sumL)
p.append(SuperPhoto(photo, cv2.imread(file)))
for ligne in range(int(count/nbCol)):
for colonne in range(nbCol):
photo = p[ligne * nbCol + colonne]
button = Button(self.scrollable_canvas.interior, image=photo.miniature, command=photo.agrandir)
button.grid(row=ligne, column=colonne)
if __name__ == "__main__":
root = Tk()
root.title("VISU")
root.geometry("+0+0")
L= []
interface = Main_frame(fenetre_principale=root)
root.update_idletasks()
print(root.winfo_width())
print(root.geometry())
interface.mainloop()
So, I except this program to work like a classic directory display, with the columns that change automatically when we resize the window and with the scrollbar that follow it.
If you have any solutions it will really help me ..
You can try it, just put some jpeg pictures in a directory and change the folder variable with the link of your directory.
Thanks in advance for your help, if you have any questions to understand more clearly what i've said don't hesitate.
Each time the root window is resized, a <Configure> event is triggered. Catch it as follows:
def resize(event):
root.update_idletasks()
#update all image sizes here if needed
#all widgets can be 're-grided' here based on new width and height of root window
root.bind('<Configure>', resize)
If you want to ensure that your window cannot be resized, use the following:
root.resizable(False, False)

Resources