0

I was trying to place buttons with images inside them with Tkinter, when I noticed some extremely strange behaviour. Take the following example:

This code works:

import tkinter as tk

root = tk.Tk()
root.geometry('720x480')
toolbar = tk.Frame(root, height=32)
toolbar.pack()

button1 = tk.Button(toolbar, text="1")
img1 = tk.PhotoImage(file="img1.png")
button1.config(image=img1)
button1.pack(side=tk.LEFT)

This code doesn't: (The difference is on the second last line)

import tkinter as tk

root = tk.Tk()
root.geometry('720x480')
toolbar = tk.Frame(root, height=32)
toolbar.pack()

button1 = tk.Button(toolbar, text="1")
button1.config(image=tk.PhotoImage(file="img1.png"))
button1.pack(side=tk.LEFT)

What?? Why should it matter whether the value of tk.PhotoImage(file="img1.png") is stored in a variable or not, button1.config(image= receives the same thing at the end of the day regardless of how it gets there.

Additionally, it seems competing variable names for these images cause errors as well, see below:

This works:

import tkinter as tk

root = tk.Tk()
root.geometry('720x480')
toolbar = tk.Frame(root, height=32)
toolbar.pack()

button1 = tk.Button(toolbar, text="1")
img1 = tk.PhotoImage(file="img1.png")
button1.config(image=img1)
button1.pack(side=tk.LEFT)

button2 = tk.Button(toolbar, text="2")
img2 = tk.PhotoImage(file="img2.png")
button2.config(image=img2)
button2.pack(side=tk.LEFT)

root.mainloop()

This doesn't: (Only the second button gets an image)

import tkinter as tk

root = tk.Tk()
root.geometry('720x480')
toolbar = tk.Frame(root, height=32)
toolbar.pack()

button1 = tk.Button(toolbar, text="1")
img = tk.PhotoImage(file="img1.png")
button1.config(image=img)
button1.pack(side=tk.LEFT)

button2 = tk.Button(toolbar, text="2")
img = tk.PhotoImage(file="img2.png")
button2.config(image=img)
button2.pack(side=tk.LEFT)

root.mainloop()

An explanation as to why having these values stored in a variable as opposed to just being directly passed to the function has any effect at all would be greatly appreciated.

EDIT:

More weirdness...

This works:

import tkinter as tk

root = tk.Tk()
root.geometry('720x480')
toolbar = tk.Frame(root, height=32)
toolbar.pack()

button1 = tk.Button(toolbar, text="1")
img1 = tk.PhotoImage(file="img1.png")
button1.config(image=img1)
button1.pack(side=tk.LEFT)

root.mainloop()

This doesn't:

import tkinter as tk

root = tk.Tk()
root.geometry('720x480')
toolbar = tk.Frame(root, height=32)
toolbar.pack()


def add_button(frame, txt, im_file):
    button = tk.Button(frame, text=txt)
    img = tk.PhotoImage(file=im_file)
    button.config(image=img)
    button.pack(side=tk.LEFT)

add_button(toolbar, 1, 'img1.png')

root.mainloop()
Recessive
  • 1,780
  • 2
  • 14
  • 37

1 Answers1

0

I naïvely thought that pythons garbage collector couldn't be the issue, because I have never had any issues with it in my 5 years of programming with python, but first time for everything :)

The issue is the GC throws the image away, likely because the image isn't strictly used in python but rather passed to C through some binding, and so the GC is able to "miss" its use later on and throws it away (I'm guessing). Anyway, by forcing the garbage collector to keep it around the issue goes away (it's why naming it in a variable kept it around), this is easiest done with a list:

import tkinter as tk

root = tk.Tk()
root.geometry('720x480')
toolbar = tk.Frame(root, height=32)
toolbar.pack()

im_lst = []

def add_button(frame, txt, im_file, im_lst):
    button = tk.Button(frame, text=txt)
    im_lst.append(tk.PhotoImage(file=im_file))
    button.config(image=im_lst[-1])
    button.pack(side=tk.LEFT)

add_button(toolbar, 1, 'img1.png', im_lst)
add_button(toolbar, 2, 'img2.png', im_lst)

Related questions:

Simple GUI with images

Python, tkinter: Why is this jpeg not showing?

Python Tkinter PhotoImage

Recessive
  • 1,780
  • 2
  • 14
  • 37
  • It is related to [reference counts](https://docs.python.org/3.9/c-api/intro.html#reference-counts). – acw1668 May 20 '21 at 06:22