9.6 Embedding Inline GIFs Using TkinterCredit: Brent Burley 9.6.1 Problem
You need to embed GIF images inside your source code—for use in Tkinter
9.6.2 SolutionA lively Tkinter GUI can include many small images. However, you probably shouldn't require that a small GIF file be present for each of these; ensuring the presence of many small files is a bother, and if they're missing, your GUI may be unusable or look wrong. Fortunately, you can construct Tkinter PhotoImage objects with inline data. It's easy to convert a GIF to inline form as Python source code:
import base64
print "icon='''\\n" + base64.encodestring(open("icon.gif").read( )) + "'''"
You can then split the result into lines of reasonable length: icon='''R0lGODdhFQAVAPMAAAQ2PESapISCBASCBMTCxPxmNCQiJJya/ISChGRmzPz+/PxmzDQyZ DQyZDQyZDQyZCwAAAAAFQAVAAAElJDISau9Vh2WMD0gqHHelJwnsXVloqDd2hrMm8pYYiSHYfMMRm 53ULlQHGFFx1MZCciUiVOsPmEkKNVp3UBhJ4Ohy1UxerSgJGZMMBbcBACQlVhRiHvaUsXHgywTdyc LdxyB gm1vcTyIZW4MeU6NgQEBXEGRcQcIlwQIAwEHoioCAgWmCZ0Iq5+hA6wIpqislgGhthEAOw== ''' and use it in Tkinter:
import Tkinter
root = Tkinter.Tk( )
iconImage =
Tkinter.PhotoImage(master=root, data=icon)
Tkinter.Button(image=iconImage).pack( )
9.6.3 DiscussionThe basic technique is to encode the GIF with the standard Python module base64 and store the results as a string literal in the Python code, which will be passed to Tkinter's PhotoImage . The current release of PhotoImage supports GIF and PPM, but inline data is supported only for GIF. You can use file='filename' , instead of data=string , for either GIF or PPM. You must keep a reference to the PhotoImage object yourself; it is not kept by the Tkinter widget. If you pass it to Button and forget it, you will become very frustrated! Here's an easy workaround for this minor annoyance:
def makeImageWidget(icondata, *args, **kwds):
if args: klass = args.pop(0)
else: klass = Tkinter.Button
class Widget(klass):
def _ _init_ _(self, image, *args, **kwds):
kwds['image']=image
klass._ _init_ _(self, args, kwds)
self._ _image = image
return Widget(Tkinter.PhotoImage(data=icondata), *args, **kwds)
Using this, the equivalent of the example in the recipe becomes: makeImageWidget(icon).pack( )
The
master
argument on
PhotoImage
is optional; it defaults to the default application window. If you create a new application window (by calling
Tk
again), you will need to create your images in that context and supply the master argument, so the
makeImageWidget
function would need to be updated to let you
9.6.4 See AlsoInformation about Tkinter can be obtained from a variety of sources, such as Pythonware's An Introduction to Tkinter , by Fredrik Lundh (http://www.pythonware.com/library), New Mexico Tech's Tkinter reference (http://www.nmt.edu/tcc/help/lang/python/docs.html), and various books. |
9.7 Combining Tkinter and Asynchronous I/O with ThreadsCredit: Jacob Halln 9.7.1 ProblemYou need to access sockets, serial ports, and do other asynchronous (but blocking) I/O while running a Tkinter-based GUI. 9.7.2 SolutionThe solution is to handle a Tkinter interface on one thread and communicate to it (via Queue objects) the events on I/O channels handled by other threads:
import Tkinter
import time
import threading
import random
import Queue
class GuiPart:
def _ _init_ _(self, master, queue, endCommand):
self.queue = queue
# Set up the GUI
console = Tkinter.Button(master, text='Done', command=endCommand)
console.pack( )
# Add more GUI stuff here depending on your specific needs
def processIncoming(self):
"""Handle all messages currently in the queue, if any."""
while self.queue.qsize( ):
try:
msg = self.queue.get(0)
# Check contents of message and do whatever is needed. As a
# simple test, print it (in real life, you would
# suitably update the GUI's display in a richer fashion).
print msg
except Queue.Empty:
# just on general principles, although we don't
# expect this branch to be taken in this case
pass
class ThreadedClient:
"""
Launch the main part of the GUI and the worker thread. periodicCall and
endApplication could reside in the GUI part, but putting them here
means that you have all the thread controls in a single place.
"""
def _ _init_ _(self, master):
"""
Start the GUI and the asynchronous threads. We are in the main
(original) thread of the application, which will later be used by
the GUI as well. We spawn a new thread for the worker (I/O).
"""
self.master = master
# Create the queue
self.queue = Queue.Queue( )
# Set up the GUI part
self.gui = GuiPart(master, self.queue, self.endApplication)
# Set up the thread to do asynchronous I/O
# More threads can also be created and used, if necessary
self.running = 1
self.thread1 = threading.Thread(target=self.workerThread1)
self.thread1.start( )
# Start the periodic call in the GUI to check if the queue contains
# anything
self.periodicCall( )
def periodicCall(self):
"""
Check every 200 ms if there is something new in the queue.
"""
self.gui.processIncoming( )
if not self.running:
# This is the brutal stop of the system. You may want to do
# some cleanup before actually shutting it down.
import sys
sys.exit(1)
self.master.after(200, self.periodicCall)
def workerThread1(self):
"""
This is where we handle the asynchronous I/O. For example, it may be
a 'select( )'. One important thing to remember is that the thread has
to yield control pretty regularly, by select or otherwise.
"""
while self.running:
# To simulate asynchronous I/O, we create a random number at
# random intervals. Replace the following two lines with the real
# thing.
time.sleep(rand.random( ) * 1.5)
msg = rand.random( )
self.queue.put(msg)
def endApplication(self):
self.running = 0
rand = random.Random( )
root = Tkinter.Tk( )
client = ThreadedClient(root)
root.mainloop( )
9.7.3 Discussion
This recipe shows the
Tkinter, like most other GUIs, is best used with all graphic commands in a single thread. On the other hand, it's far more efficient to make I/O channels block, then wait for something to happen, rather than using nonblocking I/O and having to poll at regular intervals. The latter approach may not even be available in some cases, since not all data sources support nonblocking I/O. Therefore, for generality as well as for efficiency, we should handle I/O with a separate thread, or more than one. The I/O threads can communicate in a safe way with the main, GUI-handling thread through one or more
Queue
s. In this recipe, the GUI thread still has to do some polling (on the
Queue
s), to check if something in the
Queue
needs to be
This recipe lets a worker thread block in a
select
(simulated by random sleeps in the recipe's example worker thread). Whenever something arrives, it is received and inserted in a
Queue
. The main (GUI) thread
This recipe seems to solve a common problem, since there is a question about how to do it a few times a month in
comp.lang.python
. There are other solutions, involving synchronization between threads, that let you solve such problems without polling (the
root.after
call in the recipe). Unfortunately, such solutions are
9.7.4 See AlsoDocumentation of the standard library modules threading and Queue in the Library Reference ; information about Tkinter can be obtained from a variety of sources, such as Pythonware's An Introduction to Tkinter , by Fredrik Lundh (http://www.pythonware.com/library), New Mexico Tech's Tkinter reference (http://www.nmt.edu/tcc/help/lang/python/docs.html), and various books. |