Search code examples
pythontkintertkinter-entry

Tkinter cannot draw to another window


I want to do something that should be rather simple, but I'm struggling to make it work.

Basically I have 2 Tkinter windows (canvas_tk and control_tk).

In canvas_tk I want to show an image and draw a circle on top of it.

In control_tk I have an Entry to input the radius of the circle to be drawn.

In my code the critical line is at the very bottom of the code:

self.panel = tk.Label(canvas_tk, image=image)

If I make the label in the control_tk or if I dont specify the parent, it actually works fine and draws the circles in the control_tk window However I want the circles to be drawn in the canvas_tk window

self.panel = tk.Label(image=image)              # Works
self.panel = tk.Label(control_tk, image=image)  # Works
self.panel = tk.Label(canvas_tk, image=image)    # Fails

Here's my minimal code:

import tkinter as tk
from PIL import Image, ImageTk, ImageDraw

control_tk = tk.Tk()
canvas_tk = tk.Tk()
control_tk.geometry("300x300")
canvas_tk.geometry("900x900")

class Drawing:
    def __init__(self):
        # Numerical entry with a variable traced with callback to "on_change" function
        self.radius_var = tk.IntVar()
        self.radius_var.trace_variable("w", self.on_change)
        tk.Entry(control_tk, textvariable=self.radius_var).grid()

        # Initialize image and panel
        self.image = Image.new('RGB', (1000, 1000))
        self.panel = None

        # mainloop for the two tkinter windows
        control_tk.mainloop()
        canvas_tk.mainloop()

    def on_change(self, *args):
        print("Value changed")   # to check that function is being called


        draw = ImageDraw.Draw(self.image)
        draw.ellipse((50-self.radius_var.get(), 50-self.radius_var.get(),
                      50+self.radius_var.get(), 50+self.radius_var.get()),
                     outline='blue', width=3)
        image = ImageTk.PhotoImage(self.image)

        if self.panel:  # update the image
            self.panel.configure(image=image)
            self.panel.image = image
        else:   # if it's the first time initialize the panel
            self.panel = tk.Label(canvas_tk, image=image)
            self.panel.image = image
            self.panel.grid(sticky="w")

Drawing()

And the traceback error basically complains about image not existing

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\lab\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 508, in get
    return self._tk.getint(value)
_tkinter.TclError: expected integer but got ""

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\lab\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 1705, in __call__
    return self.func(*args)
  File "C:/GIT/142-277-00_pyScuti2/test.py", line 29, in on_change
    draw.ellipse((50-self.radius_var.get(), 50-self.radius_var.get(),
  File "C:\Users\lab\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 510, in get
    return int(self._tk.getdouble(value))
_tkinter.TclError: expected floating-point number but got ""
Value changed
Value changed
Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\lab\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 1705, in __call__
    return self.func(*args)
  File "C:/GIT/142-277-00_pyScuti2/test.py", line 38, in on_change
    self.panel = tk.Label(canvas_tk, image=image)
  File "C:\Users\lab\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 2766, in __init__
    Widget.__init__(self, master, 'label', cnf, kw)
  File "C:\Users\lab\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 2299, in __init__
    (widgetName, self._w) + extra + self._options(cnf))
_tkinter.TclError: image "pyimage1" doesn't exist

Solution

  • First of all, you should not use multiple Tk() instances. Second, you better not using trace() on tracking input change, use bind('<Return>', ...) to trigger drawing after user enters value and press Enter key. Below is a modified code based on yours:

    import tkinter as tk
    from PIL import Image, ImageTk, ImageDraw
    
    control_tk = tk.Tk()
    control_tk.geometry("300x300+800+600")
    
    canvas_tk = tk.Toplevel() # use Toplevel instead of Tk
    canvas_tk.geometry("900x900+10+10")
    
    class Drawing:
        def __init__(self):
            # Numerical entry with a variable traced with callback to "on_change" function
            self.radius_var = tk.IntVar()
            entry = tk.Entry(control_tk, textvariable=self.radius_var)
            entry.grid()
            # binding Enter key on entry to trigger the drawing
            entry.bind('<Return>', self.on_change)
    
            # Initialize image and panel
            self.image = Image.new('RGB', (1000, 1000))
            self.panel = None
    
            # mainloop for the main window
            control_tk.mainloop()
    
        def on_change(self, *args):
            try:
                radius = self.radius_var.get()
            except:
                print('Invalid number')
                return
    
            print("Value changed")   # to check that function is being called
    
            draw = ImageDraw.Draw(self.image)
            draw.ellipse((50-radius, 50-radius, 50+radius, 50+radius),
                         outline='blue', width=3)
            image = ImageTk.PhotoImage(self.image)
    
            if self.panel:
                self.panel.configure(image=image)
            else:
                self.panel = tk.Label(canvas_tk, image=image)
                self.panel.grid(sticky='w')
            self.panel.image = image
    
    Drawing()