Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use an image-button defined inside an object with TKinter

I'm trying to build a tkinter button with an image as background inside an object. It doesn't make any sense why the second implementation doesn't work !

Here are 3 very simple examples ; Who can explain the reason why the second implementation is not working?

(Python 3.6.4 :: Anaconda, Inc.)

1. Button created globally.

Works like a charm...

from tkinter import *
from PIL import Image, ImageTk
from numpy import random
w = Tk()
def cb():
    print("Hello World")
image = ImageTk.PhotoImage(image=Image.fromarray(random.random((50,50))))
b = Button(w, text="text", command=cb, image=image)
b.pack()
w.mainloop()

2. Button created inside the object A with a background image

The button doesn't work when clicked and doesn't display the image :(. There's clearly a problem but I don't understand it...

from tkinter import *
from PIL import Image, ImageTk
from numpy import random
w = Tk()
class A():
    def __init__(self, w):
        image = ImageTk.PhotoImage(image=Image.fromarray(random.random((50,50))))
        b = Button(w, text="text", command=self.cb, image=image)
        b.pack()

    def cb(self):
        print("Hello World")

a = A(w)
w.mainloop()

3. Button created inside the object A without a background image

The button works properly, but I would like to display the image as well

from tkinter import *
from PIL import Image, ImageTk
from numpy import random
w = Tk()
class A():
    def __init__(self, w):
        image = ImageTk.PhotoImage(image=Image.fromarray(random.random((50,50))))
        b = Button(w, text="text", command=self.cb)#, image=image)
        b.pack()

    def cb(self):
        print("Hello World")

a = A(w)
w.mainloop()
like image 439
Jav Avatar asked Apr 19 '26 23:04

Jav


2 Answers

You have 2 problems here.

The first problem is the image is not being saved after __init__. You probably know you need to save a reference to the image for it to be used in tkinter. You may not know that in a class if you do not assign the image to a class attribute it will not save the image after __init__.

So to fix the first issue you need change this:

image = ImageTk.PhotoImage(image=Image.fromarray(random.random((50,50))))

To this:

# add self. to make it a class attribute and keep the reference alive for the image.
self.image = ImageTk.PhotoImage(image=Image.fromarray(random.random((50,50))))

The 2nd issue you may not notice here is that your text will not display while loading an image. This is because you need to add the argument compound in order for tkinter to display both an image and text in the button. That said you also need to update the image argument to include the new self.image.

So change this:

b = Button(w, text="text", command=self.cb, image=image)

To this:

# added compound and change text color so you can see it.
b = Button(w, compound="center" , text="text", fg="white", command=self.cb, image=self.image)

Results:

enter image description here

like image 151
Mike - SMT Avatar answered Apr 21 '26 12:04

Mike - SMT


I think I understood what happened. Thanks to the linked question what's happening in the second case is that your image gets garbage collected once the __init__ method is finished. As a result your image is not available anymore to the root application, so it cannot be binded to it. The way to solve it is to make it a class attribute:

class A():
    def __init__(self, w):
        self.image = ImageTk.PhotoImage(image=Image.fromarray(random.random((50,50))))
        b = Button(w, text="text", command=self.cb, image=self.image)
        b.pack()

    def cb(self):
        print("Hello World")
like image 40
toti08 Avatar answered Apr 21 '26 12:04

toti08