Im trying to make tkinter popups with a image in them, a image from bytes. It works the first time, but when i try to make a second popup after clicking the first one away, it gives the error
_tkinter.TclError: image "pyimage1" doesn't exist
this is probably caused because pyimage1 gets garbage collected, i tried storing it in a list but it didn't work
this is my code:
import PIL
import socketio
import json
from tkinter import *
import io
from PIL import ImageTk, Image
import threading
import tkinter as tk
tk_image = []
sio = socketio.Client()
@sio.event
def connect():
print('Connected to server')
@sio.event
def disconnect():
print('Disconnected from server')
@sio.on('command')
def command(data):
cmd_data = json.loads(data)
reqcmd = cmd_data["command"]
args = cmd_data["args"]
flags = cmd_data["flags"]
try:
args0 = args[0]
except IndexError:
args0 = ""
try:
args1 = args[1]
except IndexError:
args1 = ""
try:
flags0 = flags[0]
except IndexError:
flags0 = ""
try:
flags1 = flags[1]
except IndexError:
flags1 = ""
root = Tk()
root.withdraw()
if reqcmd == "popup":
def show_image_popup(image_bytess, argss1):
try:
global tk_image
popup = tk.Toplevel(root)
popup.title(argss1)
popup.geometry("400x400")
image = PIL.Image.open(io.BytesIO(bytes(image_bytess)))
image = image.resize((400, 400))
print(tk_image)
if len(tk_image) == 0:
tk_image.append(ImageTk.PhotoImage(image))
else:
tk_image[0].paste(image)
print(tk_image[0])
image_label = tk.Label(popup, image=tk_image[0])
image_label.pack()
image_label.configure(image=tk_image[0])
image_label.image = tk_image
popup.protocol("WM_DELETE_WINDOW", lambda: (image_label.destroy(), popup.destroy()))
except (Exception, AttributeError) as e:
print(e)
sio.emit("msg", e)
if flags0 == "img":
threading.Thread(target=show_image_popup, args=(flags1, args0)).start()
root.mainloop()
sio.connect('')
sio.wait()
I tried to emulate your socket with a button press. It's a poor attempt but the script does work. It sends bytes to the popup, and displays the image, no matter how many times you redisplay. The only thing I see that I am doing differently than you is storing the image in a global dict. I'm also not bothering to store a reference on the Label
. The label get's destroyed as soon as the window is closed so, storing anything on the Label
is a waste of time.
from PIL import ImageTk, Image
import tkinter as tk, random, io
""" fake socket """
socket = {}
for picpath, name in (('snips/image1.png', 'image1'), ('snips/image2.png', 'image2')):
with open(picpath, 'rb') as img:
socket[name] = img.read()
def dosocket():
name = random.choice(list(socket.keys()))
show_image_popup(socket[name], name)
""" end fake socket """
images = {}
root = tk.Tk()
def show_image_popup(imgbytes, title):
global images
popup = tk.Toplevel(root)
popup.title(title)
popup.geometry("400x400")
image = Image.open(io.BytesIO(imgbytes)).resize((400, 400))
if not (img:=images.get('default')):
img = images['default'] = ImageTk.PhotoImage(image)
else: img.paste(image)
image_label = tk.Label(popup, image=img)
image_label.pack()
popup.protocol("WM_DELETE_WINDOW", lambda: (image_label.destroy(), popup.destroy()))
button = tk.Button(root, text='click', command=dosocket)
button.grid()
root.mainloop()
Edit: I just realized your actual problem. Every time you call command
you create a new instance of Tk. You need to refactor your code to use ONE and ONLY ONE instance of Tk.