I’m working on a little gui app that will eventually (hopefully) add a watermark to a photo. But right now I’m focused on just messing around with tkinter and trying to get some basic functionality down.

I’ve managed to display an image. Now I want to change the image to whatever is in the Entry widget (ideally, the user would put an absolute path to an image and nothing else). When I click the button, it makes the image disappear. I made it also create a plain text label to see if that would show up. It did.

Okay, time to break out the big guns. Add a breakpoint. py -m pdb main.py. it works. wtf?

def change_image():
    new_image = Image.open(image_path.get()).resize((480, 270))
    new_tk_image = ImageTk.PhotoImage(new_image)

with the breakpoint, the button that calls change_image works as expected. But without the breakpoint, it just makes the original image disappear. Please help me understand what is happening!

edit: all the code

import io
import tkinter as tk
from pathlib import Path
from tkinter import ttk

from PIL import ImageTk
from PIL import Image

from LocalImage import Localimage
from Layout import Layout

class State:
    def __init__(self) -> None:
        self.chosen_image_path = ""

    def update_image_path(self):
        self.chosen_image_path = image_path.get()

def change_image():
    new_image = Image.open(image_path.get()).resize((480, 270))
    new_tk_image = ImageTk.PhotoImage(new_image)

TEST_PHOTO_PATH = "/home/me/bg/space.png"
PIL_TEST_PHOTO_PATH = "/home/me/bg/cyberpunkcity.jpg"
pil_test_img = Image.open(PIL_TEST_PHOTO_PATH).resize((480,270))
# why does the resize method call behave differently when i inline it
# instead of doing pil_test_img.resize() on a separate line?

root = tk.Tk()

root.title("Watermark Me")
mainframe = ttk.Frame(root, padding="3 3 12 12")
mainframe.grid(column=0, row=0, sticky="NWES")

layout = Layout(mainframe)

image_path = tk.StringVar()
tk_image = ImageTk.PhotoImage(pil_test_img)
test_image_label = ttk.Label(image=tk_image)

entry_label = ttk.Label(mainframe, text="Choose an image to watermark:")
image_path_entry = ttk.Entry(mainframe, textvariable=image_path)
select_button = ttk.Button(mainframe, text="Select",
hide_button = ttk.Button(mainframe, text="Hide", command= lambda x=test_image_label:
test_text_label = ttk.Label(mainframe, text="here i am")
empty_label = ttk.Label(mainframe, text="")

for child in mainframe.winfo_children():
    child.grid_configure(padx=5, pady=5)

entry_label.grid(column=0, row=0)
image_path_entry.grid(column=1, row=0)
hide_button.grid(column=0, row=3)
select_button.grid(column=0, row=4)
test_image_label.grid(column=0, row=5)
empty_label.grid(column=0, row=6)


  • bjorney
    2 months ago

    You shouldn’t have to worry about tracking object lifetimes in a garbage-collected language like Python, but Tkinter is just…weird about it.

    Don’t know if this is the case with TK, but this is 100% the cause of a lot of bugs working with Qt in python. If you don’t explicitly keep a reference to all your python objects, they can get GCd, and then when the Qt loop tries to access them you get a crash.

    • tunetardis
      2 months ago

      Ah so it’s not only Tkinter then. Interesting. Maybe this is one of the reasons why Python-specific GUIs like Kivy came about? When I first started learning Python though, I came under the impression that Tkinter was the “official” Python GUI with privileged status in the standard library, so I started there and was surprised at how it actually doesn’t play all that nice with the language.

      Oh well. I’m glad OP brought it up because I’m sure everyone who’s tried to write a GUI in Python for the first time hits this before long and has that same wtf moment. :)