Tkinter Summary
This is a quick reference for Tkinter. It is intended as a supplement to proper reference material, not as a replacement. The depth ranges from the simple to the arcane. I tend to add new topics as I find myself having difficulty locating information I need.
import Tkinter
root = Tkinter.Tk()
# set up your interface, then run it with:
root.mainloop()
Basic Widgets: Toplevel, Frame, Button, Checkbutton, Entry, Label, Listbox, OptionMenu, Photoimage, Radiobutton, Scale.
Fancy Widgets: Canvas, Text. These have taggable contents that act like objects.
Hard to Use: Menu, Menubutton, Scrollbar.
Tk Variables: StringVar, IntVar, DoubleVar, BooleanVar. These are data containers needed by certain widgets, are handy with many others for easy access to contents, and can trigger callbacks when their data is changed.
Extra Modules (not loaded by "import Tkinter")
tkFont defines the Font class for font metrics and getting names of fonts. If you set a widget's font to a Font object, then changing the Font live-updates the widget.
FileDialog defines FileDialog, LoadFileDialog, SaveFileDialog. Here is an example:
fdlg = FileDialog.LoadFileDialog(root, title="Choose A File")
fname = fdlg.go() # opt args: dir_or_file=os.curdir, pattern="*", default="", key=None)
if file == None: # user cancelled
tkColorChooser defines askcolor(initialcolor), which returns a user-chosen color.
tkSimpleDialog defines askinteger(title, prompt, initialvalue, minvalue, maxvalue), askfloat and askstring.
Examine the source code in .../Lib/lib-tk to find others and see examples of use.
pack(side="top/right/bottom/left", expand=0/1, anchor="n/nw/w...", fill="x/y/both")
- By default, widgets are centered within a parcel; use anchor to change this.
- By default, widgets do not grow to fill the parcel; use expand and fill to change this.
grid(row, column, rowspan=?, columnspan=?, sticky="news", ipadx=?, ipady=?, padx=?, pady=?)
columnconfigure(row, weight=?, minsize=?, pad=?)
columnconfigure(column, weight=?, minsize=?, pad=?)
- Each row and column shrinks to fit the smallest element in that row and column.
- By default, widgets are centered within a cell and do not grow to fill it; use sticky to change this.
- By default rows and columns to not grow when the user resizes a window; set nonzero weights using rowconfigure and columnconfigure to change this.
- Rowconfigure and columnconfigure are also helpful to solve a common problem: spanning can cause rows or columns to increase in size. Suppose you have two columns that are 20 and 30 pixels wide, respectively. Suppose you also want to grid a "wide widget" 100 pixels wide without making the existing columns wider. Simply gridding the object to span the two columns will force them to grow so the total spans 100 pixels (they each gain 25 pixels, for a total of 45 and 55 pixels, respectively). Gridding the wide object to span more columns reduces the problem (by distributing the extra pixels among more columns) but does not solve it. The solution is to have the wide widget span at least one extra column and use columnconfigure to configure the extra column to have weight 1.
Events are described as a string: "<modifiers-type-qualifier>".
Event types include:
- mouse (modifiers B1, etc. specify which mouse button):
- ButtonPress (can be abbreviated Button, modifier Double)
- ButtonRelease
- Enter
- Leave
- Motion
- keyboard (modifiers include Control, Shift, Meta...)
- KeyPress (can be abbreviated Key)
- KeyRelease
- window (in Tk language a window is any widget):
- Configure (window resized, moved, etc.)
- Expose
- FocusIn
- FocusOut
- Map
- Unmap
Warning: which widgets see which events varies by platform.
Qualifiers for KeyPress and KeyRelease are keysyms. Letters and numbers are used as is, but punctuation and all other keys require special case-sensitive names, including: comma, period, dollar, asciicircum, numbersign, exclam, Return, Escape, BackSpace, Tab, Up, Down, Left, Right... When in doubt, run an interactive test such as the example given below.
To trigger a callback when a widget sees an event, use bind:
widget.bind(event, callback)
- The callback receives exactly one argument: the event.
- The event includes the following attributes (not a complete list):
- keysym: the keysm, as described above (KeyPress and KeyRelease events)
- height, width (Configure event)
- serial: the serial number of the event (all events)
- type: a number representing the event type (all events)
- time: the time of the event (all events)
- widget: the widget that received the event (all events)
- x, y: position of mouse pointer relative to the widget (mouse events and perhaps all events)
- x_root, y_root: position of mouse pointer relative to root window (mouse events and perhaps all events)
- To stop event propogation, return the string "break" from your callback; this may be needed if you are trying to override default widget behavior.
- There are other types/levels of bindings, for instance you can bind an event to all instances of a particular class (e.g. all Buttons). See An Introduction to Tkinter for more info.
Here is an example that displays the key symbol for any key pressed:
#!/usr/local/bin/Python
"""Displays the keysym for each KeyPress event as you type."""
import Tkinter
root = Tkinter.Tk()
root.title("Keysym Logger")
def reportEvent(event):
print 'keysym=%s, keysym_num=%s' % (event.keysym, event.keysym_num)
text = Tkinter.Text(root, width=20, height=5, highlightthickness=2)
text.bind('<KeyPress>', reportEvent)
text.pack(expand=1, fill="both")
text.focus_set()
root.mainloop()
A Destroy event callback is fired too late to clean up widgets or prevent window destruction. To do either of these, use the WM_DELETE_WINDOW protocol handler. (But if you are trying to clean up as your application exits, use the standard python library "atexit", instead; it's simpler!) This replaces the default behavior, so you must destroy the window yourself if you actually want it destroyed. The callback receives no arguments.
toplevel.protocol("WM_DELETE_WINDOW", callback)
There are two other protocols: WM_SAVE_YOURSELF and WM_TAKE_FOCUS. I'm not sure what use they are.
Widgets such as Button support a "command" parameter. The callback receives no arguments. This is by far the best way to fire a callback when a Button is pressed because there is no single mouse event that does the same job. It is a pain that "command" callbacks send the callback function no data, but you can easily use a Callback Shim to rectify the situation.
To trigger a callback when a Tk variable is changed, use trace_variable:
traceName = tkvar.trace_variable(mode, callback)
- Mode is one of "r", "w" or "u" (read, write or update)
- The callback receives 3 arguments: varName (name of Tk variable), index (always ''?), mode
- The callback can modify the contents of the variable without triggering another callback
- To get or modify a variable by name, use anywdg.getvar(varName), anywdg.setvar(varName, value)
- To delete a callback, use tkvar.trace_delete(traceName)
- See Tkinter Folklore for more information.
After: Timed Events and Animation
To trigger a callback after a specified delay, e.g. for animation, use after:
widget.after(timems, callback, arg1, arg2...)
- arg1, arg2... represent zero or more optional extra arguments
- The callback receives only those optional extra arguments, if specified
- The callback is only called once for each call to after.
The question is how to communicate over a socket (especially how to read data) without tying up the event loop or being very inefficient. There are several options:
File handlers
A file handler will call a callback when a file or socket has data to read or write. File handlers are simple and well integrated into Tkinter. Unfortunately they do not work on Windows (at least as of Python 2.3.4 and Windows XP). But if your program only has to run on unix and/or MacOS X, they are worth considering because they are so easy to use.
wdg.tk.createfilehandler(file_or_socket, mask, callback)
where wdg is any Tkinter widget (if you don't have one handy, create a new frame for the purpose).
- Mask: any ORed combination of: Tkinter.READABLE, Tkinter.WRITABLE, Tkinter.EXCEPTION
- The callback receives exactly two arguments: file_or_socket, mask
- A file may have only one handler. Calling createfilehandler again simply replaces the old handler.
- To permanently remove a handler, call deletefilehandler(file_or_socket)
- To temporarily remove a handler, call createfilehandler(file_or_socket, None). This is better than deleting the file handler and recreating it.
- I changed the notation on 2003-05-06. The old way used tkinter; it worked fine through Python 2.2.x but broke with Python 2.3 (at least if tcl was built with threads). The new way works with Python 2.3 and Python 2.2.2 and probably quite a ways back before that.
Tcl sockets
Tcl sockets are fully cross-platform, but a bit more difficult to use because they require some tcl code. Still, it's not very difficult and well worth the effort to get fuly cross-platform code. For an example of how to use tcl sockets, download my RO package and look at RO.Comm.TkSocket. See also this posting by Matthew Cincera that I used to get started (with thanks to Stephane Beland for the pointer).
Twisted Framework
Twisted Framework is a free cross-platform networking library that works with several different GUI toolkits (nor does it actually require a GUI toolkit). It has a very good reputation. I confess I have not yet used it myself, but expect to switch to it someday.
I find I often wish to pass extra data to a callback function, in addition that that normally given. For instance the Button widget sends no arguments to its command callback, but I may want to use one callback function to handle multiple buttons, in which case I need to know which button was pressed.
The way to handle this is to define the callback function just before you pass it to the widget and include any extra information that you require. Unfortunately, like most languages, Python doesn't handle the mixing of early binding (information known when the function is defined) and late binding (informtation known when the function is called) particularly well. I personally find the easiest and cleanest solution is:
- Write my callback function to take all desired data as arguments.
- Use a callback shim class to create a callable object that stores my function and the extra arguments and does the right thing when called. In other words, it calls my function with the saved data plus the data that the caller supplies.
I hope the example given below makes this clearer.
The callback shim I use is RO.Alg.GenericCallback, which is available in my RO package. A simplified version that does not handle keyword arguments is given in the example below. All shim code is based on a python recipe by Scott David Daniels, who calls this "currying a function" (a term that is probably more common than "callback shim").
#!/usr/local/bin/Python
"""Example showing use of a callback shim"""
import Tkinter
def doButton(buttonName):
"""My desired callback. I'll need a callback shim
because Button command callbacks receive no arguments.
"""
print buttonName, "pressed"
class SimpleCallback:
"""Create a callback shim. Based on code by Scott David Daniels
(which also handles keyword arguments).
"""
def __init__(self, callback, *firstArgs):
self.__callback = callback
self.__firstArgs = firstArgs
def __call__(self, *args):
return self.__callback (*(self.__firstArgs + args))
root = Tkinter.Tk()
buttonNames = ("Button 1", "Button 2", "Button 3")
for name in buttonNames:
callback = SimpleCallback(doButton, name)
Tkinter.Button(root, text=name, command=callback).pack()
root.mainloop()
- "line.col" line = line number, starting with 1 (!); col = column # starting with 0
- "current" character under mouse pointer
- "insert" character after cursor
- "end" just after end
- "@x,y" character under specified screen position
- "tag.first", "tag.last" first char, just after last char with specified tag
These may be modified by appending the following strings:
- " +/- n chars"
- " +/- n lines"
- " linestart"
- " linend"
- " wordstart"
- " wordend"
For example "1.0 lineend" referst to the end of first line.
- integers starting from 0 (or string representations)
- "end", "insert", "@x,y" (see Text widget, above)
- "anchor" (anchor point of selection), "sel.first", "sel.last"
There are several ways of retrieving configuration information from a widget:
- Treat it like a dictionary: aWidget["text"]. (Note: this works both for setting and getting information.)
- Use the cget method: aWidget.cget("text").
- You can retrieve a dictionary of all settings by calling configure with no arguments: aWidget.configure().
In all cases, each setting is returned as a string. This can be a major headache. For instance boolean values will be "0" or "1" (both of which are logically true, in Python). The problem is even worse when trying to retrieve things that are normally Tkinter objects, such as Tk variables or wigets. The following example illustrates the problem (though in such a simple situation you already have the original Tk objects) and some of the solutions discussed below:
import Tkinter
root = Tkinter.Tk()
aVar = Tkinter.StringVar()
aLabel = Tkinter.Label(textvar = aVar)
aLabel["textvar"]
'PY_VAR0'
root.setvar(aLabel["textvar"], "foo")
aLabel.getvar(aLabel["textvar"])
'foo'
str(aLabel)
'.8252000'
root.nametowidget(str(aLabel))
<Tkinter.Label instance at 0x7dea60>
aLabel.master
<Tkinter.Label instance at 0x7dea60>
root.master
None
The solution depends on what you are retriving:
- A variable, such as a StringVar. If you have a variable's name you can read or write the variable using
getvar(name_of_var)
and setvar(name_of_var, new_value)
. It is also possible to create a Tkinter variable that works like the original, but it's ugly enough that I recommend using getvar and setvar instead (or holding onto the original Tkinter variable). If you really want to do it, first create a new Tkinter variable, e.g. v = Tkinter.StringVar()
and then set it's _name
property: v._name = name_of_variable
. The problem with this is it creates a new Tk variable that you never use and it relies on an undocumented property of Tkinter variables. (I am not even entirely sure it is safe, due to garbage collection issues.)
- A displayable widget (such as a Button). If you have a widget's name, you can convert it to a Tkinter object using the method
nametowidget(name_of_wdg)
. Also, you can retrieve the master of a widget using wdg.master (which returns a Tkinter widget instead of the name of a widget).
- A tkFont.Font object (which corresponds to a Tk "named font"). If you create a tkFont.Font object and use it to set the font for a widget or class of widgets, then manipulating the tkFont.Font object changes the font of the widget(s). This is very handy for user-settable preferences and the like. If you have a tk named font's name, you can create a new tkFont.Font object based on it by specifying the name argument and exists=True. (If exists=False, then a new tk named font is created which will be deleted when the tkFont.Font object goes away, so be careful!).
- Note: getvar, setvar and nametowidget can be called on any Tkinter widget that you happen to have handy, e.g.
any_widget.getvar(name_of_var)
.
General hints:
- Pack the scrollbar before the widget being scrolled. Then if the window shrinks too far to show everything, the scrollbar will remain visible.
- For complex geometries, use frames to group widgets; this is usually easier and safer than getting too fancy with the packer or gridder. But be very careful to use the right master for each widget or you'll get a mess, perhaps even a grid/pack fight (infinite loop).
Here are some common pitfalls in Tkinter and python, and how to avoid them:
- Callback functions must be specified without parenthesis and thus without arguments. If you specify parenthesis, Python calls the function just once (as it sets up the GUI), and the returned value is used as the callback function. This is a very common beginner mistake. One reason it is so common is that users often want to specify some arguments along with the callback function. A nice way to do this is to use a callback shim.
- Never try to pack and grid widgets in the same master. This causes an infinite loop! If your application freezes while painting a window, this is the first thing to check. It can also be difficult to find. If you are puzzled, look carefully at the master you specified for each of your widgets; you may have specified the wrong master or perhaps forgotten to specify a master at all (it defaults to root).
- Some Tcl/Tk objects (not including widgets) are deleted when the associated Tkinter object disappears. This often causes confusion. Some examples:
- Images: if images or icons disappear from Tk widgets, you probably didn't keep a reference to the Tkinter representation of the image.
- Variables (StringVar, etc.): if your Radiobuttons all become unset, check to see if the variable still exists. That is the most visible issue I know of with variables. If you lose an Entry textvariable, at least the existing text remains. Trace_variable appears to keep a reference to both the variable and the function, so you need not necessarily keep your own references when using trace_variable.
- tkFont.Font objects: these are Tkinter representations tcl named fonts and are useful for configuring fonts in sets of widgets. Keep a reference if you wish to reconfigure the widgets later. Otherwise you can safely let the tkFont.Font object go away because the widgets retain their last configuration.
- Note: if you query a widget for a variable or other item that has been deleted, you get the former name of the deleted item, rather than any indication that the item is gone. So debugging this problem can be tricky. One thing you can try is converting the name to a new Tkinter object (e.g. using root.getvar(name) or tkFont.Font(name, exists=True)); this will fail if the tcl object has been deleted.
- All Tkinter access must be from the main thread (or, more precisely, the thread that called mainloop). Violating this is likely to cause nasty and mysterious symptoms such as freezes or core dumps. Yes this makes combining multi-threading and Tkinter very difficult. The only fully safe technique I have found is polling (e.g. use
after
from the main loop to poll a threading Queue
that your thread writes). I have seen it suggested that a thread can safely use event_create
to communicate with the main thread, but have found this is not safe.
- Never use a mutable object (such as a list, dictionary or just about anything except a number, string, tuple or None) as the default value of an argument. The value of the default can easily end up changing (if your function alters the argument internally). Thus
def foo(alist=[]):...
is a disaster waiting to happen. To avoid this problem, use None for the default value, then test for None internally and change it to [] (or {} or whatever).
Less common Tkinter pitfalls:
- If you execute code as a result of tracing a Variable and that code results in another Variable being set, that "other Variable" may not be set until after the code finishes executing. I found this when executing Text.search and trying to use the count variable. (I have reported this as a bug, but suspect it may be a feature.)
- An Introduction to Tkinter by Fredrik Lundh is an excellent place to start. Unfortunately, it is unfinished and appears likely to remain so.
- "Python in a Nutshell" by Alex Martelli has a brief but nice introduction to Tkinter. It is also a very good Python reference book; in my opinion any serious Python programmer should own "Python in a Nutshell" and/or David Beazley's "Python Essential Reference".
- Tkinter Folkore contains information on parts of Tkinter that I had trouble finding good documentation for.
- Tcl/Tk is similar enough to Tkinter (and the available information on Tkinter is so sparse) that it can be very helpful to have a good Tcl/Tk reference handy. I don't know the full range of available resources, but two of my favorites are:
- Python and Tkinter Programming, a book by John Grayson, is presently the only full book on the subject. Be warned that it spends a lot of time on Pmw (Python MegaWidgets), at the expense of Tkinter. Also, some important information is difficult to find or missing. But if you are doing a lot with Tk you will definitely need it. I usually consult the reference section in the back first, and if I still have questions then I try a good Tcl/Tk reference (see above).
- Read the code for Tkinter and other files in the same directory. You'll find useful goodies and it may solve some puzzles for you. (One way to find the Tkinter directory is to import Tkinter and print Tkinter.__file__).
Written by Russell Owen. Last updated 2006-10-20 (revised the Hints section including a hint suggested by Troels Therkelsen and revised Resources). This document may be freely distributed but must not be sold.