How to maintain multiple references for PhotoImage objects - python

I am trying to display two image objects in my Text widget. Initially, both objects showed up as empty images. After some research, I learned that I need to keep a reference to the image. I added an extra line to my code. I now see the 2nd image displayed correctly, however the 1st image is still blank.
I suspect my "reference" is being over written during the for loop. Is this the case? And if so, how would I maintain multiple references if I need to display multiple image objects inside a Text widget?
Here is my code so far:
from Tkinter import *
from PIL import Image, ImageTk
import os
class TestApp:
def __init__(self, parent):
self.myParent = parent
self.main_container = Frame(parent)
self.main_container.pack()
self.text_box = Text(self.main_container, height=50, width=80)
self.text_box.pack()
image_names = ['img1.jpg', 'img2.jpg']
for name in image_names:
img = Image.open(os.getcwd() + '\\' + name)
img_obj = ImageTk.PhotoImage(img)
self.text_box.image_create(INSERT, image=img_obj)
self.text_box.insert(END, "\n")
# Added this extra reference
self.text_box.image = img_obj
root = Tk()
root.title("This is a test app")
mainapp = TestApp(root)
root.mainloop()

Yes, your reference is being overwritten.
The simplest might be to just add the reference to a list. For example:
class TestApp:
def __init__(...):
...
self.images = []
...
for name in image_names:
...
img_obj = ImageTk.PhotoImage(img)
self.images.append(img_obj)

Related

Create Enum referencing a list with TkImages

I've got a couple of images in a folder and each of these gets resized and turned into an image for Tkinter. I want to use these images in other python scripts as a enum.
This is how I create the images:
import os
from PIL import Image, ImageTk
from enum import Enum
#Resize All Images and turn them into TkImages
def ResizeImages():
PieceImages = []
for images in os.listdir('Resources'):
cImage = Image.open('Resources/' + images)
cImage = cImage.resize((80, 80), Image.ANTIALIAS)
PieceImages.append(ImageTk.PhotoImage(cImage))
return PieceImages
#Called from outside to create PNGS list AFTER Tkinter.Tk() was called
def InitializeImages():
global PNGS
PNGS = ResizeImages()
#This is the part where I'm lost
class Black(Enum):
global PNGS
Queen = PNGS[3]
King = PNGS[4]
Tower = PNGS[5]
I want to create a Tkinter Button with these images on like this:
#PieceImages is the .py file above
import PieceImages import Black
form tkinter import *
root = Tk()
root.geometry('800x800')
someButton = tk.Button(root, image=Black.Tower).place(x=100, y=100)
My Question is:
Is it even possible to create enums of this type and if so, how?
I'd recommend staying away from global variables; you can pass arguments to a class directly:
class Black():
def __init__(self, pngs):
self.name1 = pngs[0]
self.name2 = pngs[1]
Instantiate by passing pngs:
black = Black(pngs)
Then black.name1, black.name2 etc will be your desired images.

ttk Image buttons only showing images for last items in collection

I am building a series of mutli-element "buttons" based on a dynamic data set.
I have the list generating and all elements are correctly being placed in their respective TTK elements -- except the logos. These are only appearing in the last multi-element button.
Below is my menu.py file. I have added a comment about the images in question.
import globals
import os
from data import Data
from utils import Image as imageUtils
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
class CreateMenu(tk.Canvas):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
menuItems = Data.getSchedule().games
# ---------------------------------------------------------------------
self.canvas = tk.Canvas(self, bg = '#000000', width = globals.screenWidth, height = globals.screenHeight)
self.canvas.pack()
for item in menuItems:
self.childFrame = tk.Frame(self.canvas, width=(buttonWidth - padding), height=buttonHeight, bg='#888888')
# ---------------------------------------------------------------------
self.awayLogo = imageUtils.webImage(item.away.logo)
print(self.awayLogo)
self.awayLogoImageFile = Image.open(self.awayLogo)
self.awayLogoImageFile = self.awayLogoImageFile.resize((thumbnailWidth - 16, thumbnailHeight - 16), Image.ANTIALIAS)
self.awayLogoImage = ImageTk.PhotoImage(self.awayLogoImageFile, format='png', width=thumbnailWidth, height=thumbnailHeight)
# The image is only getting added in the last instance of "item".
# In other items, the cards exist.
# I have confirmed that the images exist in the source location.
# The button below is placed, but is missing the image (except in the last instance of the "item" where it is rendered correctly)
self.awayLogoButton = ttk.Button(self.childFrame, compound=tk.CENTER, image=self.awayLogoImage, command=lambda link=item.link: selectGame(link))
self.awayLogoButton.pack(ipady = 1)
self.awayLogoButton.place(x=0, y=0, width=thumbnailWidth, height=thumbnailHeight)
self.awayNameButton = ttk.Button(self.childFrame, text=item.away.name, command=lambda link=item.link: selectGame(link))
self.awayNameButton.pack(ipady = 1)
self.awayNameButton.place(x=thumbnailWidth, y=0, height=buttonHeight/2, width=teamTextWidth)
# ---------------------------------------------------------------------
self.childFrame.bind("<Return>", lambda event, link=item.link: selectGame(link))
self.childFrame.focus_set()
self.childFrame.pack(ipady = 1)
self.childFrame.place(x = x, y = y)
if (x == (globals.screenWidth - buttonWidth)):
x = 0
y = y + buttonHeight
else:
x = x + buttonWidth
Any ideas on how to correct the images not appearing on all the other instances of item?
The issue is that python garbage collects all of your images and deletes them from python's memory as soon as you can no longer hold a reference to them.
To fix your issue you will need to add all of the images to a list like this:
self.tk_images = []
for ... in ...:
tk_image = ImageTk.PhotoImage(...)
...
self.tk_images.append(tk_image)
That way you can keep a reference to those images so python doesn't garbage collect them.

python tkinter not display image in label

I am new to python.I have tried a code on how to display Texbox,image and button.But the image not displays
Please rectify my code to display the image!
My code:
import Tkinter
from Tkinter import *
class myproject(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self)
self.button2()
self.text()
self.image()
def button2(self):
button2 = Tkinter.Button(self, text = "hello")
button2.grid(column=5,row=7)
def text(self):
text = Tkinter.Text(self, height=3, width=31)
text.grid(column=1,row=3)
text.insert(END, "Wiilliam Skakespeare")
def image(self):
logo = PhotoImage(file="linux.gif")
w1 = Tkinter.Label(self, image=logo)
w1.grid(column=5,row=7)
app = myproject(None)
app.mainloop()
You need to save the PhotoImage as a class variable so the reference can stay in memory. The following method for image() should work:
def image(self):
self.logo = Tkinter.PhotoImage(file="linux.gif")
w1 = Tkinter.Label(self, image=self.logo)
w1.grid(column=5,row=7)
This page provides a more in-depth explanation: Effbot PhotoImage. Specifically this section:
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.

I can't display text over my tkinter image

I am trying to display text on top of my image but I cannot do do this, can anyone help please.
Code:
# import Image and the graphics package Tkinter
import Tkinter
import Image, ImageTk
class simpleapp_tk(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def initialize(self):
## def create_widgets(self):
# create welcome label
label1 = Tkinter.Label(self, text = "Update User")
label1.grid(row = 0, column = 1, columnspan = 2, sticky = 'W')
# open a SPIDER image and convert to byte format
im = Image.open('C:\Users\JOHN\Desktop\key.jpg')
root = Tkinter.Tk() # A root window for displaying objects
# Convert the Image object into a TkPhoto object
tkimage = ImageTk.PhotoImage(im)
Tkinter.Label(root, image=tkimage).pack() # Put it in the display window
root.mainloop() # Start the GUI
The Label constructor takes a parameter compound. Pass the constructor both the image and text, and pass in compound as Tkinter.CENTER to overlap the text onto the image. Documentation for this feature is at http://effbot.org/tkinterbook/label.htm
import Tkinter
import Image, ImageTk
# open a SPIDER image and convert to byte format
im = Image.open(r'C:\Users\JOHN\Desktop\key.jpg')
root = Tkinter.Tk() # A root window for displaying objects
# Convert the Image object into a TkPhoto object
tkimage = ImageTk.PhotoImage(im)
Tkinter.Label(root, image=tkimage, text="Update User", compound=Tkinter.CENTER).pack() # Put it in the display window
root.mainloop() # Start the GUI
Also note, you're not supposed to mix pack and grid. You should choose one or the other. Reference: http://effbot.org/tkinterbook/grid.htm
P.S. just in case you meant you want the text to be vertically higher than the image, you can use the same code as above, except set compound=Tkinter.BOTTOM.

Python Tkinker - showing a jpg as a class method not working

I'm trying to show a jpg image as background for a GUI thing I'm building.
I can get it to work in a single method:
from Tkinter import *
from PIL import Image, ImageTk
class MakeGUI(object):
master = None
w = None
def __init__(self):
self.canvasSizeY = 400 #height
self.canvasSizeX = 640 #width
def setupCanvas(self):
"""
preps the canvas for drawing.
"""
self.master = Tk()
self.w = Canvas(self.master, width=self.canvasSizeX, height=self.canvasSizeY)
self.w.config(bg='white')
image = Image.open("background.jpg")
photo = ImageTk.PhotoImage(image)
self.w.create_image(0,0, image=photo, anchor=NW)
self.w.pack()
mainloop()
def main():
makeGUI = MakeGUI()
makeGUI.setupCanvas()
if __name__ == '__main__':
main()
But when I try and make the canvas in one method, and show the canvas in another, it doesn't show the jpg (when I've been testing, I've created and shown & text and rectangles using this approach):
from Tkinter import *
from PIL import Image, ImageTk
class MakeGUI(object):
master = None
w = None
def __init__(self):
self.canvasSizeY = 400 #height
self.canvasSizeX = 640 #width
def setupCanvas(self):
"""
preps the canvas for drawing.
"""
self.master = Tk()
self.w = Canvas(self.master, width=self.canvasSizeX, height=self.canvasSizeY)
self.w.config(bg='white')
image = Image.open("background.jpg")
photo = ImageTk.PhotoImage(image)
self.w.create_image(0,0, image=photo, anchor=NW)
def showImage(self):
"""
pushes the image to the screen
"""
self.w.pack()
self.w.mainloop()
def main():
makeGUI = MakeGUI()
makeGUI.setupCanvas()
if __name__ == '__main__':
main()
I want to use the GUI dynamically to show some text as I work through some editing, so I'm interested to understand what I've got wrong before I get too far into the build in case its a showstopper...
The most obvious problem is that in the second case you are never calling showImage. Even after you do call that function, your image probably won't show up. Images will be garbage-collected if there isn't a reference to them. It may seem like there's a reference because you're adding it to a canvas, but that isn't enough.
You'll need to do something like:
self.photo = ImageTk.PhotoImage(image)
Finally, I recommend that you take the call to mainloop out of showImage. mainloop must always be called exactly once, so most typically it is the last line of code in your program, or the last line of code in your main function.
A more common way to make a Tkinter application is to subclass either the Tk object or a Frame object, rather than having your main application be a generic object. For example:
class MyApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
...
self.setupCanvas(...)
...
if __name__ == "__main__":
app = MyApp()
app.mainloop()

Categories

Resources