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)
    test_image_label.configure(image=new_tk_image)
    breakpoint()

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_image_label.configure(image=new_tk_image)
    breakpoint()

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",
                           command=change_image)
hide_button = ttk.Button(mainframe, text="Hide", command= lambda x=test_image_label:
                  layout.hide_image(x))
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)


image_path_entry.insert(0,TEST_PHOTO_PATH)
image_path_entry.focus()
breakpoint()



root.mainloop()
  • @grue
    link
    English
    36 months ago

    What I have found with Tkinter in general is that it can be rather quirky in terms of dealing with object ownership. I don’t know why this is? You shouldn’t have to worry about tracking object lifetimes in a garbage-collected language like Python, but Tkinter is just…weird about it.

    My understanding is very limited, but I think the reason is that TKinter isn’t Python, it’s Tk (i.e., Tcl and/or C). It’s a real thin binding around non-garbage-collected stuff.

    • @[email protected]
      link
      fedilink
      English
      26 months ago

      This sounds highly plausible. Somehow, Tkinter objects are not always first class Python objects, and the object management (such as it is) is happening outside the scope of Python.