[Baypiggies] Images in Tkinter, help before I go nuts

Ian Zimmerman itz at buug.org
Tue Mar 22 05:53:43 EDT 2016


So, as preparation for another project which shall remain unmentioned
for now, I am trying to write a simple image viewer in Tkinter.  The one
aspect that keeps it from being completely trivial is that it is not
driven by mousey actions but by commands from stdin, which naturally
must be read in a different thread.  This is the aspect that makes it
valuable in the larger project, so it must stay in.

Here is what I have so far:

# -*-Python-*-

import Tkinter as T
import json
import threading as TH
import sys
import Queue as Q


SIZES = ('width', 'height')


class Work(object):

    def __init__(self, **kwargs):
        self.kwargs = kwargs

    def __getattr__(self, k):
        return self.kwargs[k]

    def __getitem__(self, k):
        return self.kwargs[k]


def front(iwid, q):
    line = sys.stdin.readline().strip()
    while line:
        work = Work(**json.loads(line))
        q.put(work)
        iwid.event_generate('<<%s>>' % work.kind)
        line = sys.stdin.readline().strip()

def handle_title(iwid, e, q):
    work = q.get()
    iwid._root().title(work.title)

def handle_size(iwid, e, q):
    work = q.get()
    iwid.config(**{k: work[k] for k in SIZES})

def handle_quit(iwid, e, q):
    work = q.get()
    iwid._root().destroy()

def handle_image(iwid, e, q):
    work = q.get()
    width, height = [iwid[k] for k in SIZES]
    piltk = T.PhotoImage(file=work.name)
    iwid.config(image=piltk)

def main():
    q = Q.Queue()
    iwid = T.Label()
    piltk = T.PhotoImage(file='curious_tree.gif')
    iwid.config(image=piltk)
    iwid.grid(column=0, row=0)
    iwid.bind('<<Title>>', lambda e: handle_title(iwid, e, q))
    iwid.bind('<<Size>>', lambda e: handle_size(iwid, e, q))
    iwid.bind('<<Quit>>', lambda e: handle_quit(iwid, e, q))
    iwid.bind('<<Image>>', lambda e: handle_image(iwid, e, q))
    ft = TH.Thread(target=lambda: front(iwid, q))
    ft.daemon = True
    ft.start()
    iwid.mainloop()

if __name__ == "__main__":
    main()

To summarize, front() - which runs in a subthread - reads commands from
stdin in JSON format, one per line, and manufactures events that it
sends to the Tk main loop.  The events let the main loop know there is
work to do; the details of the work it retrieves from a Queue.  This
basic structure seems to work; the Title, Size and Quit events are
processed perfectly.

My problem is with the Image event/command, which of course is the main
one.  The JSON command for it looks like

{"kind": "Image", "name": "curious_tree.gif"}

When the main loop receives the corresponding event (i.e. '<<Image>>'),
it is supposed to change the image displayed in the label widget
(iwid).  What actually happens, though, is that the widget gets resized
to the correct native size of the image (so evidently the image file is
found and read), but the image is _not_ drawn; and when I send the next
Size event, I get this:

 [112+3]coverlet$ python ./image_handler.py 
{"kind": "Size", "width": 200, "height": 200}
{"kind": "Image", "name": "onboat.gif"}
{"kind": "Size", "width": 200, "height": 200}
Exception in Tkinter callback
Traceback (most recent call last):
  File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1437, in __call__
    return self.func(*args)
  File "./image_handler.py", line 62, in <lambda>
    iwid.bind('<<Size>>', lambda e: handle_size(iwid, e, q))
  File "./image_handler.py", line 42, in handle_size
    iwid.config(**{k: work[k] for k in SIZES})
  File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1229, in configure
    return self._configure('configure', cnf, kw)
  File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1220, in _configure
    self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
TclError: image "pyimage1" doesn't exist

Now notice the code in main() where iwid is constructed.  It is almost
literally the same as the code in the event handler (the only difference
being that in the handler the filename comes from the JSON command, but
I traced that part and the name passes through correctly).  And the
initialization code *works*, the widget initially displays the file
curious_tree.gif exactly as intended.

I found a few Stack Overflow and similar discussions where that
infuriating error message pops up, and people suggest it has something
to do with creating multiple images, each in a different main loop.  But
I should add that in my case I get the exception (and no redraw) even
when I remove the initial setting, i.e. when the event handler's image
is the first one constructed.  I believe that is visible in the above
backtrace, that's why the name is "pyimage1".

Am I missing some referesh step that must be done to the widget or to
the image?  Or wth is going on here?

Thanks.

-- 
Please *no* private copies of mailing list or newsgroup messages.
Rule 420: All persons more than eight miles high to leave the court.


More information about the Baypiggies mailing list