Using instance attributes to create images in tkinter - python

I'm working on a simple tkinter program, and am trying to create a callback function that will take an image (imported using PIL and stored as a class attribute) and draw it on the canvas when the corresponding button is clicked. Each click should create a new Bacteria object, reflected by the creation of a new image on the canvas (in the actual program, the new objects are also appended to an array and used later in the program's execution--the code here is something of a simplification).
The following runs without errors (except for a file-not-found error stemming from the fake file name used for posting) but, unfortunately, no images draw on the canvas when the button is clicked. The code works as intended when the image is imported and stored as an attribute of the MainWindow class--it only seems to fail when imported/stored as an attribute of the Bacteria class.
import tkinter as tk
import random
from PIL import Image
from PIL import ImageTk
class MainWindow(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent, background = "white")
self.pack()
self.canvas_height = 500
self.canvas_width = 1000
self.canvas = tk.Canvas(self, height = self.canvas_height, width = self.canvas_width)
self.canvas.grid(row = 0, column = 0)
self.launch_button = tk.Button(self, text = "Haz clic!", width = 25, command = self.callback)
self.launch_button.grid(row = 1, column = 0, sticky = "W")
def callback(self):
test_bact = Bacteria()
x_pos = random.randint(0,1000)
y_pos = random.randint(0,500)
self.canvas.create_image(x_pos, y_pos, image = test_bact.imageTk)
class Bacteria:
def __init__(self):
self.image = Image.open('testBacterium.png')
self.imageTk = ImageTk.PhotoImage(image=self.image)
root = tk.Tk()
app = MainWindow(root)
app.mainloop()
I'm sort of stumped on this. Can anyone offer any insight on what's going wrong?

From documentation of canvas.create_image() -
create_image(position, **options) [#] Draws an image on the canvas.
image= The image object. This should be a PhotoImage or BitmapImage,
or a compatible object (such as the PIL PhotoImage). The application
must keep a reference to the image object.
(Emphasis mine)
So most probably, either canvas object does not keep a reference to the image itself, or it keeps a weak reference . In either case, in a nutshell what is happening is that after to call canvas.create_image() and the callback() method ends, you are no longer holding any reference to the image object (used in the create_image() method) , and hence its not showing up.
In your case, when you are keeping the reference to the image in Bacteria class , what is happening is -
You are creating the Bacteria object and loading the image and storing there. You are just creating the Bacteria object - test_bact - as a local variable for callback() method.
Then you are using test_bact.imageTk for the image argument to canvas.create_image() method.
Now, the callback() ends, and hence there is no longer any reference to test_bact and so it gets garbage collected. Also , since the only reference to imageTk object was in the Bacteria object, that also gets garbage collected, and hence there are no more references to the image object in your application.
From what you want to achieve, it seems like you should store the bacteria object as an instance variable of MainWindows class.

Related

unable to eliminate a global variable

I have a fairly lengthy GUI image viewing program, the "globals" started to get out of hand and I have eliminated them all but one. The simplified code below shows the global variable art. with it in place the image is displayed, without it - a grey screen. I would appreciate any help in understanding what is going on
from tkinter import *
from PIL import ImageTk,Image
root = Tk()
def image():
global art
path="c:/Google Drive/Art Images/0030#Van Tromp, going about to please his Masters.jpg"
image=Image.open(path)
art = ImageTk.PhotoImage(image)
label.grid()
label.configure(image=art)
label=Label(root,bg="grey")
image()
root.mainloop()
Your issue is that when creating a Photoimage, you need to keep a reference to that image, meaning you need to assign that image a variable name that can be accessed elsewhere. In your case, the variable art, without using a global, stays in the function. This means that when the function finished, art is destroyed. An easy fix is to return the variable art, making the output of the function art, and thus bringing the variable into the main code. This is what effbot has to say about that:
When you add a PhotoImage or other Image object to a Tkinter widget, you must keep your own reference to the image object. If you don’t, the image won’t always show up.
The problem is that the Tkinter/Tk interface doesn’t handle references to Image
objects properly; the Tk widget will hold a reference to the internal object, but Tkinter does not. When Python’s garbage collector discards the Tkinter object, Tkinter tells Tk to release the image. But since the image is in use by a widget, Tk doesn’t destroy it. Not completely. It just blanks the image, making it completely transparent
To fix this, you could use a global variable (which can be accessed elsewhere in the main code, thus becoming a 'reference'), or you could do something like this:
from tkinter import *
from PIL import ImageTk,Image
root = Tk()
def image():
label.grid()
path="c:/Google Drive/Art Images/0030#Van Tromp, going about to please his Masters.jpg"
image=Image.open(path)
art = ImageTk.PhotoImage(image)
label.configure(image=art)
return art
label=Label(root,bg="grey")
image()
root.mainloop()
In that code, your image is returned by the function and assigned a variable in the main code, making is a 'reference' without globals.
You can find more info here
Your function, image(), is being used to output your image, and to do that, you're assigning it to the variable 'art'. An image object is created, but when your function ends, since you aren't returning anything, that object ceases to exist:
y = 5
def getx():
x = 3
getx()
a = y + x
This code won't work, because x is local to the function getx(). Since I'm not doing anything with it, when the function ends, so does the variable. What I can do, though, is pass data out of the function, like this:
y = 5
def getx():
x = 3
return x
x = getx()
a = y + x
In your code, tkinter needs for the image object to exist when it runs, but while you created it within your function, you let the function end without doing anything with it. Naming art a global variable effectively allows your function to return your object to a variable outside of the function. Another way to do it, though would be:
root = Tk()
def image():
path="c:/Google Drive/Art Images/0030#Van Tromp, going about Masters.jpg"
image=Image.open(path)
art = ImageTk.PhotoImage(image)
label.grid()
label.configure(image=art)
return art
label=Label(root,bg="grey")
art = image()
root.mainloop()

Why doesn't a local name with a PhotoImage object work, for a tkinter label image?

I have created a python class which inherits Tk from tkinter library.
I want to add a label with an image to it but it only works when I create a Photoimage as variable to 'self'.
This code works:
class HMIDrawer(Tk):
def __init__(self):
super().__init__()
self.frame = Frame(self)
self.img = PhotoImage(file='resources/platoontomtom_empty.png')
self.label = Label(self.frame, image=self.img, bg='white')
self.label.pack()
self.frame.pack()
self.mainloop()
And this code doesn't work:
class HMIDrawer(Tk):
def __init__(self):
super().__init__()
self.frame = Frame(self)
img = PhotoImage(file='resources/platoontomtom_empty.png')
self.label = Label(self.frame, image=img, bg='white')
self.label.pack()
self.frame.pack()
self.mainloop()
Can anyone explain why the first code does work and the second code doesn't?
PhotoImage reads the image data from file and stores it in memory as a pixel buffer. The reference of this buffer has to be persistent as long as the application runs (or at least, as long the Label widget showing the image exists).
If you put the result of PhotoImage in a local variable (e.g. img, as in the second code), the garbage collector is likely to destroy the data referenced by this local variable. On the other hand, if you store the reference of your pixel buffer in an attribute (e.g. self.img, as in the first code) the garbage collector will not destroy your data as long as self exists, which means as long as the application runs...
If your GUI uses many images, a usual practice is to create an attribute self.images that puts all required images either in a tuple or a dictionary (whether you prefer indexing them by numbers or by strings), that ensures that all images will be kept in memory.

Tkinter loading images in the frame

I am using Python 2.7 and want to load a .gif logo on the Tkinter frame but there is a problem that it open two windows all the time (one empty and one with logo).
Codes:
import Tkinter
root = Toplevel()
logo = PhotoImage(file="D:\\.....\\....\\****.gif")
w1 = Label(root, compound = CENTER, image = logo).pack(side="right")
root.mainloop()
how can I have only one window with my logo?
Every tkinter app needs a Tk() window a.k.a root for other widgets to exist. If you don't create it explicitly, it will be created implicitly. Your empty window is that implicitly created Tk() window and the other one is Toplevel() you created.
So you need to change this line
root = Toplevel()
to
root = Tk()
Additionally, please keep the reference of your image.
When you add a PhotoImage or other Image object to a Tkinter widget,
you must keep your own reference to the image object. If you don’t,
the image won’t always show up.
The problem is that the Tkinter/Tk interface doesn’t handle references
to Image objects properly; the Tk widget will hold a reference to the
internal object, but Tkinter does not. When Python’s garbage collector
discards the Tkinter object, Tkinter tells Tk to release the image.
But since the image is in use by a widget, Tk doesn’t destroy it. Not
completely. It just blanks the image, making it completely
transparent…

Python tkinter image not showing up [duplicate]

This question already has answers here:
Why does Tkinter image not show up if created in a function?
(5 answers)
Closed 4 years ago.
I am trying to display an image in a window...seems simple enough right? Well I have a big bug!
I have this exact same code in one file:
import Tkinter
root = Tkinter.Tk()
canvas = Tkinter.Canvas(root)
canvas.grid(row = 0, column = 0)
photo = Tkinter.PhotoImage(file = '/Users/Richy/Desktop/1.gif')
image1 = canvas.create_image(0,0, image=photo)
root.mainloop()
It works.
I have this in part of a bigger file:
def officialPictureWindow(self):
t = Toplevel(self)
t.wm_title("Official Image")
self.__canvas3 = Canvas(t)
self.__canvas3.grid(row = 0, column = 0)
photo = PhotoImage(file = '/Users/Richy/Desktop/1.gif')
image1 = self.__canvas3.create_image(0,0, image=photo)
It doesn't work!
That function is called when someone presses a button on a menubar I have. All the other menubar buttons I have operate properly and show their windows. There's no images in the others though.
This gives no no error. Just a blank screen. Does anyone know why?
You need to keep an additional reference to photo so it doesn't get prematurely garbage collected at the end of the function. An Introduction to Tkinter explains further:
Note: When a PhotoImage object is garbage-collected by Python (e.g. when you return from a function which stored an image in a local variable), the image is cleared even if it’s being displayed by a Tkinter widget.
To avoid this, the program must keep an extra reference to the image object. A simple way to do this is to assign the image to a widget attribute, like this:
label = Label(image=photo)
label.image = photo # keep a reference!
label.pack()
In your case, you could attach the image to your self variable, or maybe the canvas. It doesn't really matter, as long as it is assigned to something.
self.image = photo
#or:
self.__canvas3.image = photo

Tkinter vanishing PhotoImage issue [duplicate]

This question already has answers here:
Why does Tkinter image not show up if created in a function?
(5 answers)
Closed 4 years ago.
I am trying to display an image in a window...seems simple enough right? Well I have a big bug!
I have this exact same code in one file:
import Tkinter
root = Tkinter.Tk()
canvas = Tkinter.Canvas(root)
canvas.grid(row = 0, column = 0)
photo = Tkinter.PhotoImage(file = '/Users/Richy/Desktop/1.gif')
image1 = canvas.create_image(0,0, image=photo)
root.mainloop()
It works.
I have this in part of a bigger file:
def officialPictureWindow(self):
t = Toplevel(self)
t.wm_title("Official Image")
self.__canvas3 = Canvas(t)
self.__canvas3.grid(row = 0, column = 0)
photo = PhotoImage(file = '/Users/Richy/Desktop/1.gif')
image1 = self.__canvas3.create_image(0,0, image=photo)
It doesn't work!
That function is called when someone presses a button on a menubar I have. All the other menubar buttons I have operate properly and show their windows. There's no images in the others though.
This gives no no error. Just a blank screen. Does anyone know why?
You need to keep an additional reference to photo so it doesn't get prematurely garbage collected at the end of the function. An Introduction to Tkinter explains further:
Note: When a PhotoImage object is garbage-collected by Python (e.g. when you return from a function which stored an image in a local variable), the image is cleared even if it’s being displayed by a Tkinter widget.
To avoid this, the program must keep an extra reference to the image object. A simple way to do this is to assign the image to a widget attribute, like this:
label = Label(image=photo)
label.image = photo # keep a reference!
label.pack()
In your case, you could attach the image to your self variable, or maybe the canvas. It doesn't really matter, as long as it is assigned to something.
self.image = photo
#or:
self.__canvas3.image = photo

Categories

Resources