0

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!

Jordan
  • 1
  • 1
  • 1

3 Answers3

2

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).

michaelhubbard.ca
  • 253
  • 1
  • 2
  • 10
  • Changing the __del__ method to this: – michaelhubbard.ca Sep 21 '11 at 17:32
  • I was able to catch the exception in the __del__ method, but was surprised to see what was being throw. (, TclError('can\'t invoke "image" command: application has been destroyed',), ) it looks like it was a TclError that was being thrown (yet was not caught by the __del__. This makes me wonder what the Tkinter authors were doing to not seeing this issue... will investigate further. – michaelhubbard.ca Sep 21 '11 at 17:47
0

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

T.P.
  • 2,975
  • 1
  • 21
  • 20
0

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()
Fenikso
  • 9,251
  • 5
  • 44
  • 72
  • I don't entirely understand. I need to move my root window constructor and mainloop outside of the code blocks and into the outermost part (I apologize if I get my references to parts of the program wrong) and only to call them once. I get that. I also need to create changes to the contents of the root window using callback functions, which is the part that I really don't know how to do. – Jordan May 26 '11 at 15:21
  • You understand the first part very well. See jtp's answer for how to do it, it provides a nice example. As for the callback functions: The main idea of nearly any GUI is that when you setup widget, for example Button, you can bind the event generated when the button is pushed to some function. This type of functions is called `callback`. Result is very simple, when you push the button, the function is called. I do not use Tk, so I am not really sure, but I have found a nice example, so I will add it to my answer. – Fenikso May 27 '11 at 07:19
  • As a rule of thumb, when you applications shows up, you should be stuck in `mainloop`. Anything your app does after that, should be done from callback functions. – Fenikso May 27 '11 at 07:23