I am reading a script about how to make a small game of dice rolling, and now have a difficulty understanding the last two lines below:
To me, it seems the image was already updated by ".configure(image = DiceImage)"
So why we have to update the image again by the last line "ImageLabel.image = DiceImage"?
ImageLabel = tkinter.Label(root, image = DiceImage)
def rolling_dice():
DiceImage = ImageTk.PhotoImage(Image.open(random.choice(dice)))
ImageLabel.configure(image = DiceImage)
ImageLabel.image = DiceImage
The line ImageLabel.image = DiceImage is not configuring the widget. Tkinter images get destroyed by the garbage collector if a reference isn't saved somewhere. This results in a blank space where the image should be.
Saving it as an attribute to the widget it is associated with is the simplest solution to work around that problem.
Related
I have the following code:
import tkinter as tk
data = [['A',1],['B',2],['C',3],['D',4]]
def ranking():
root = tk.Tk()
root.focus_force()
windowWidth = root.winfo_reqwidth()
windowHeight = root.winfo_reqheight()
positionRight = int(root.winfo_screenwidth()/2 - windowWidth/2)
positionDown = int(root.winfo_screenheight()/2 - windowHeight/2)
root.geometry("+{}+{}".format(positionRight, positionDown))
for i in range(0,len(data)):
tk.Label(image=tk.PhotoImage(file="D:\\A\\" + data[i][0] + ".gif")).grid(row=data[i][1]-1, column=0)
root.mainloop()
ranking()
What I want to do is to have a random number of pictures (whatever is inserted in data at any moment), displayed within the window (in the order as indicated within the data), yet if I currently run the code it simply results in a blank window.The names of the pictures are literally A, B, C...and they display without a problem if I use something like (this is an extract from another piece of code I wrote):
img2 = tk.PhotoImage(file="D:\\A\\" + choice2 + ".gif")
label_img2 = tk.Label(image=img2)
label_img2.grid(row=2, column=1)
Any guidance would be very welcome as I am still quite new with working with tkinter!
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…
The solution is to make sure to keep a reference to the Tkinter object, for example by attaching it to a widget attribute:
for i in range(0,len(data)):
photo = tk.PhotoImage(file="D:\\A\\" + data[i][0] + ".gif")
label = tk.Label(image=photo )
label.image = photo # keep a reference!
label.grid(row=data[i][1]-1, column=0)
Taken from Why do my Tkinter images not appear?
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
Currently I'm using this snippet of code which seems pretty easy:
label = ttk.Label(mainframe)
image1 = PhotoImage(file='my_image.gif')
label['image'] = image1
label.grid(column=1, row=0)
However, if I edit the size of my_image.gif in photoshop, then run it again, the image gets stretched to the same size, and this seems to continue no matter how small I make the base image. This seems to suggest to me that the PhotoImage or something above it enforces a default size or specific minimum size. I cannot find any documentation to suggest that this is the case.
From here I found the help(PhotoImage) suggestion which I used. When in the python interpreter I run the help(PhotoImage) command and I found this:
height(self)
Return the height of the image.
type(self)
Return the type of the imgage, e.g. "photo" or "bitmap".
width(self)
Return the width of the image.
But it doesn't seem to provide me with an image sizing of any type either.
After searching all over and seeing no reference at all, i'm beginning to suspect that using images in a Label is for a specific purpose and I'm approaching this all wrong. All i'm trying to do is place a logo at the top of the window, but I want the logo to be limited in size so it doesn't take over the whole window.
Also of note is this question which seems to be lacking an answer but I too am curious if there is some documentation on it. Maybe I'm missing something obvious but I did check the python documentation and the http://www.tkdocs.com site for more information.
Apparently I made an error, but I have no clue what it was. In the end this was this code that did it for me:
from tkinter import *
from tkinter import ttk
root = Tk()
root.title("ImageTest")
label = ttk.Label(root)
image1 = PhotoImage(file='my_image.gif')
label['image'] = image1
label.grid(column=1, row=0)
root.mainloop()
It's all working now as expected.
I've been tinkering around with Python lately and wanted to make a GUI that reads from a CSV and displays it correctly.
CSV build up:
name,description,image location
steven,some guy,/res/pic/steven.gif
the first two entries should be put in text labels, and the last entry should be used as an image.
In my code I got as far as inserting the picture, which worked. But as soon as I also embedded the text label, I think the application runs into an infinity loop.
If I delete the Image from the code, the text label works and vice versa.
from Tkinter import *
from PIL import *
import os
import csv
#Functions
def insertImage(guiName,picture,x,y):
#This is the Image label insertion, delete it and Text label works
img = PhotoImage(file=entryList[picture][2])
preview = Label(guiName, image=img)
preview.img = img
preview.grid(row=x,column=y)
#This is the Text label insertion, delete it and Image Label works
Name = StringVar()
labelName = Label(mainGUI, textvariable=Name, justify=LEFT)
Name.set(entryList[picture][2])
labelName.pack()
global mainGUI
mainGUI = Tk()
mainGUI.geometry("500x500")
mainGUI.title('Index')
reader = csv.reader(open("res/test.csv", "rb"))
entryList = []
for row in reader:
entryList.append( row )
#insertImage(mainGUI,entryList[1][2],1,1)
insertImage(mainGUI,1,1,1)
#insertImage(mainGUI,2,2,1)
mainGUI.mainloop()
Does anyone have an idea what the problem might be?
The problem is that you are using grid() and pack() to position widgets within the same master widget (mainGUI). That won't work, because by default both of those geometry managers attempt to manage the size of the parent widget and end up fighting over the size (which blocks the GUI from ever appearing as a side effect).
The very latest version of Tk (the lib underneath Tkinter) will throw an error if you try to do this (finally!) but your best bet is to just use one geometry manager per parent widget. (There are some subtleties with disabling geometry propagation which can make this work, and “parent” can be a touch tricky in a few situations, but the key issue is that you're doing the wrong thing in the first place.)
Also, a single label can contain both an image and some text; see the compound option (which enables this and controls the relative placement rules).
I am making a Tkinter GUI to do nothing except call images - and of course, I have struggled to find decent tkinter documentation all along.
There is a line of my code which cannot seem to do as asked - I want to call up all the values in a dictionary and individually print and pull an image by the same name for each one before the next value is called up. I have tried dict.itervalues() and dict.values() and can't seem to figure anything out altogether...
Anyway, here is the snippet:
for key in ansDict.iterkeys(): #using the iterkeys function... kind of
x=key
root = tk.Tk() # root window created (is this in the right place?)
root.title('C H E M I S T R Y A B C\'s')
frameAns=tk.Frame(root)
frameAns.grid(row=0, column=0, sticky=tk.NW)
for i in range(len(ansDict[x])):
print '-->' + ansDict[x][i]
for value in ansDict.itervalues(): #This is the most important part
for i in range(len(value)): #pulls value list from dictionary named ansDict
picRef1 = Image.open(value[i] + '.jpg') #calls image file by the same name using PIL
photo1 = ImageTk.PhotoImage(picRef1, master=root)
button1 = tk.Button(frameAns, compound=tk.TOP, image=photo1, text=str(value[i]) + '\nClose me!', bg='white') #pulls up button onto which the image is pasted
button1.grid(sticky=tk.NW, padx=2, pady=2) #places button on grid
button1.image=photo1
root.mainloop()
Finally, at the end, it pulls up one or two images and then I get the following error:
TclError: can't invoke "image" command: application has been destroyed
and I can't figure out what is wrong. I can't move the image command, and somehow I need to "save" it so it isn't destroyed. I know there are other code errors here, but I think that if I figure out the TclError that I am getting that I can set everything else straight.
If there is an easier way to do all this please do tell!
I have looked around for a good solution to this but have yet to find the proper solution. Looking at the Tkinter.py class it looks like the Image del value is:
def __del__(self):
if self.name:
try:
self.tk.call('image', 'delete', self.name)
except TclError:
# May happen if the root was destroyed
pass
This means if you wanted to do a BRUTAL hack you could setup a PhotoImage as described in jtp's link.
photo = tk.PhotoImage(file="C:/myimage.gif")
widget["image"] = photo
widget.image = photo
Then you could just before the program exited do the following hack:
photo.name = None
This would prevent it from trying to clean itself up in the PhotoImage delete and prevent the exception from being called in the del method. I do not really recommend you do this unless your back is up against the wall, and you have no alternative.
I will continue to look into this and if I find a better solution will edit this post with a better one (hopefully someone will give the correct solution before then).
Here is one possibility, although it is structured differently than your example. It stacks the four 100 pixel square images on top of one another. I believe you need to keep a separate reference to each Image object, so I tucked them away in the images dictionary.
from Tkinter import *
import os
from PIL import Image, ImageTk
image_names = { '1':'one','2':'two','3':'three','4':'four' }
images = {}
root = Tk()
root.title("HELLO")
frm = Frame(root)
for v in image_names.itervalues():
images[v] = {}
images[v]['i'] = Image.open("%s%s.jpg" % (os.path.dirname(__file__), v))
images[v]['pi'] = ImageTk.PhotoImage(images[v]['i'])
images[v]['b'] = Button(frm, image=images[v]['pi'])
images[v]['b'].pack()
frm.pack()
mainloop()
Here is a good link discussing the PhotoImage class.
http://effbot.org/tkinterbook/photoimage.htm
It seems that you did not get the idea of Event-driven programming. You should create whole GUI once, fill it with widgets, setup the events and then enter infinite loop. The GUI should call callback functions based on your event to function binding. So those parts of your program should definitely be called just once: root = tk.Tk(), root.mainloop().
Edit: Added Event-driven programming "idea example".
from Tkinter import *
master = Tk()
def callback():
print "click!"
b = Button(master, text="OK", command=callback)
b.pack()
mainloop()