Search code examples
pythonimagetkinterpython-imaging-library

TclError: image "pyimage2" doesn't exist


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


Solution

  • 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.