Search code examples
pythontkinter

Why does this simple tkinter widget not work?


I'm having trouble making interactive images with tkinter in Python, and I've produced a minimal example which I don't understand. The code below is supposed to create a simple white window, which rolls down a shutter to reveal a black background as the user drags the mouse. But when I run it, the white window remains unchanged. Why is that? How can the code be modified so that the image will change?

EDIT: After experimenting, I see that the problem owes to the new image created in the move_shutter method being a local variable. When I add the line global image to the top of the method, the widget works. I don't fully understand what tkinter is doing that this should be the case. Is the local variable image somehow being garbage collected before tkinter is done with it?

import tkinter as tk
import numpy as np
from PIL import Image, ImageTk

root = tk.Tk()
array = np.ones((500, 500)) * 255
canvas = tk.Canvas(root, width=500, height=500)
image = ImageTk.PhotoImage(image=Image.fromarray(array))
canvas.pack()
canvas.create_image(0, 0, anchor="nw", image=image)

def move_shutter(event):
    """Change and redraw the image each time the mouse is moved with the left button down."""
    array[:event.y] = 0
    image = ImageTk.PhotoImage(image=Image.fromarray(array))
    canvas.pack()
    canvas.create_image(0, 0, anchor="nw", image=image)

canvas.bind("<B1-Motion>", move_shutter)
root.mainloop()

Solution

  • As you already found that it is caused by garbage collection and using global variable will fix it.

    Also:

    • it is not necessary to call canvas.pack() inside move_shutter()
    • call canvas.itemconfig(...) instead of canvas.create_image(..) to avoid creating new image item
    ...
    # save the return item ID for later use
    image_item = canvas.create_image(0, 0, anchor="nw", image=image)
    
    def move_shutter(event=None):
        global image
    
        """Change and redraw the image each time the mouse is moved with the left button down."""
        array[:event.y] = 0
        image = ImageTk.PhotoImage(image=Image.fromarray(array))
        # update the image item
        canvas.itemconfig(image_item, image=image)
    
    ...