Reputation: 1030
I am trying to refactor some Python 2.7 code into a new method in Eclipse. After I use Eclipse's Refactoring->Extract method
on the block marked by comments below, my image no longer shows up in the GUI:
from Tkinter import Tk, Button, W, E, N, S, Label, Frame
from PIL import Image, ImageTk
def myCallback():
pass
root = Tk()
# start refactor method here
root.geometry('400x400')
runButton = Button(root, text="Run",bg='green',
command=myCallback)
runButton.grid(row=2, column=0,
padx=10, pady=10)
quitButton = Button(root, text="Quit",
command=root.quit)
quitButton.grid(row=2, column=1,
padx=10, pady=10)
frame1 = Frame(width=200, height=200)
frame1.grid(row=1, column=0, columnspan=1, rowspan=1,
sticky=W+E+N+S, padx=10, pady=10)
image1 = Image.open("C:/Users/me/Pictures/house.jpg")
size = 64,64
image1.thumbnail(size, Image.ANTIALIAS)
photo1 = ImageTk.PhotoImage(image1)
label1 = Label(image=photo1)
label1.grid(row=0, column=10, columnspan=1, rowspan=1,
sticky=N+E, padx=10, pady=10)
# end refactor method here
root.mainloop()
Could someone explain why the image disappears and suggest a solution such that I can refactor without losing the image?
After refactor:
from Tkinter import Tk, Button, W, E, N, S, Label, Frame
from PIL import Image, ImageTk
def extractedMethod(root):
root.geometry('400x400')
runButton = Button(root, text="Run", bg='green', command=myCallback)
runButton.grid(row=2, column=0, padx=10, pady=10)
quitButton = Button(root, text="Quit",
command=root.quit)
quitButton.grid(row=2, column=1, padx=10, pady=10)
frame1 = Frame(width=200, height=200)
frame1.grid(row=1, column=0, columnspan=1, rowspan=1, sticky=W + E + N + S, padx=10, pady=10)
image1 = Image.open("C:/Users/me/Pictures/house.jpg")
size = 64, 64
image1.thumbnail(size, Image.ANTIALIAS)
photo1 = ImageTk.PhotoImage(image1)
label1 = Label(image=photo1)
label1.grid(row=0, column=10, columnspan=1, rowspan=1, sticky=N + E, padx=10, pady=10)
def myCallback():
pass
root = Tk()
extractedMethod(root)
root.mainloop()
Thanks.
Upvotes: 1
Views: 871
Reputation: 123541
One solution is to add one line to the beginning of theextractedMethod()
:
def extractedMethod(root):
global photo1 # added
...
The problem is that otherwisephoto1
is a local variable which is discarded as soon as the function returns, so references made to it within the function become invalid.
One might think that thelabel1 = Label(image=photo1)
statement would have incremented its reference count and prevented this from happening, but it apparently doesn't work that way. I've seen this before and personally suspect it's due to a bug inTkinter
...
Upvotes: 0
Reputation: 366103
The problem is that you're releasing the PhotoImage
object too early.
As the docs explain:
You must keep a reference to the image object in your Python program, either by storing it in a global variable, or by attaching it to another object.
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()
You might think that passing it as the image
argument to the Label
constructor would keep it alive. But it doesn't; the Label
doesn't actually keep a reference to the PhotoImage
object itself, it's only the actual label construction in the Tcl code that runs after mainloop
starts that does so. (It's similar to a weak reference, but not an intentional or explicit one, if that helps.)
So, the quick and dirty solution is to either add this to the top of extractedMethod
:
global photo1
… or add this right after the label1 = Label(image=photo1)
line:
label1.photo = photo1
A better way to avoid this is to use an OO design instead of a flat procedural design, so you have some reasonable object to own all your references.
Even the trivial version, as shown in these examples will work:
class Foo(object):
def __init__(self, root):
# put the code from extractedMethod here,
# but prefix every local variable with self,
# and do the same for myCallback.
def myCallback(self):
pass
But you're probably better off with a real OO design, where your objects actually represent something, as in the official examples:
class MyFrame(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.create_widgets()
def myCallback(self):
pass
def create_widgets(self):
# put the code from extractedMethod here,
# again prefixing things with self
Upvotes: 2