Skip to main content

Charming Python: TK programming in Python

Tips for beginners using Python's GUI library

David Mertz (mertz@gnosis.cx), President, Gnosis Software, Inc.
David Mertz writes many apocopetic articles. He can be reached at mertz@gnosis.cx; his life may be pored over at the Gnosis Web site. Suggestions and recommendations on this, past, or future columns are always welcome.

Summary:  David Mertz introduces TK and the Tkinter wrapper (Python's GUI library) with source code samples accompanied by detailed running commentary. To make life easy, he illustrates his examples with the GUI port of the Txt2Html front-end that he's used in many of his earlier articles. He assumes, of course, that you follow his column regularly. :)

Date:  01 Dec 2000
Level:  Introductory
Activity:  13018 views

I'd like to introduce you to the easiest way imaginable to start GUI programming, namely by using Scriptics' TK and Tkinter wrapper. We'll be drawing a lot of parallels with the curses library, which I covered on developerWorks in "Curses programming in Python". Both libraries have a surprisingly similar interface despite the fact that curses targets text consoles and TK implements GUIs. Before using either library, you need a basic understanding of windows and event loops and a reference to the available widgets. (Well, a good reference and a moderate amount of practice.)

Like the article on curses, this article is limited to the features of Tkinter itself. Since Tkinter comes with many Python distributions, you probably won't have to download support libraries or other Python modules. The Resources later in this article point to several collections of higher-level user interface widgets, but you can do a lot with Tkinter itself, including construction of your own high-level widgets. Learning the base Tkinter module will introduce you to the TK way of thinking, which is important even if you go on to use more advanced widget collections.

A brief description of TK

TK is a widely used graphics library most closely associated with the TCL language, both developed by John Ousterhout. Although TK started out in 1991 as an X11 library, it has since been ported to virtually every popular GUI. (It's as close as Python comes to having a "standard" GUI.) There are TK bindings (the Tkinter modules) for most popular languages now, as well as many of the smaller languages.

Before we begin, I must make a confession: I am no wizened expert at TK programming. In fact, the bulk of my TK programming experience began about three days before I started this article. Those three days were not without their challenges, but by the end I felt like I had a pretty good grasp of Tkinter . The moral here is that both TK and the Tkinter wrapper are extraordinarily well designed, user-friendly, and just about the easiest introduction to GUI programming out there.


Starting with a test application

As a test application we'll use a wrapper for Txt2Html, a file format conversion program used in many of my previous columns (see Resources). Although you can run Txt2Html in several ways, the wrapper here is based on running Txt2Html from the command line. The application runs as a batch process, with command-line arguments indicating various aspects of the conversion to be performed. (Later it might be nice to offer users the option of an interactive selection screen that leads them through conversion options and provides visual feedback of selected options before performing the actual conversion.)

tk_txt2html is based on a topbar menu with drop-downs and nested submenus. Implementation details aside, it looks a lot like the curses version discussed in "Curses programming in Python". tk_txt2html and curses_txt2html are clearly in the same ballpark, even though TK accomplishes more with less code. In TK, for example, features like menus can rely on built-in Tkinter classes instead of needing to be written from scratch.

Along with setting configuration options, the TK wrapper also includes a scrolling help box built with the TK Text widget (an about box with the Message widget) and a history window that exercises TK's dynamic geometry management. And like most interactive applications, the wrapper accepts some user input with TK's Entry widget.

Let's look at the application in action now before discussing the code any further.

Screenshot of tk_txt2html.py


Learning the basics

There are really only three things that a Tkinter program has to do:


Minimum possible [Tkinter] program
Tkinter        # import the Tkinter module
root = Tkinter.Tk()   # create a root window
root.mainloop()       # create an event loop

This is a perfectly legitimate Tkinter program (never mind that it's useless because it doesn't even manage "hello world"). The only thing this program needs to do is create some widgets to populate its root window. Thus enhanced, our program's root .mainloop() method call will handle all user interaction without further programmer intervention.


The main() function

Now let's look at the more realistic main() function of tk_txt2html.py. Notice that I prefer to perform John Grayson's import Tkinter statement rather than the from Tkinter import (see his book listed in Resources). This is not so much because I'm worried about namespace pollution (the usual caveat for from ... import statements), but rather because I want to be explicit about using Tkinter classes; I don't want to risk confusing them with my own functions and classes. I recommend you do the same thing, at least at the beginning.


tk_txt2html main() function

def main():
    global root, history_frame, info_line
    root = Tkinter.Tk()
    root.title('Txt2Html TK Shell')
    init_vars()

    #-- Create the menu frame, and menus to the menu frame
    menu_frame = Tkinter.Frame(root)
    menu_frame.pack(fill=Tkinter.X, side=Tkinter.TOP)
    menu_frame.tk_menuBar(file_menu(), action_menu(), help_menu())

    #-- Create the history frame (to be filled in during runtime)
    history_frame = Tkinter.Frame(root)
    history_frame.pack(fill=Tkinter.X, side=Tkinter.BOTTOM, pady=2)

    #-- Create the info frame and fill with initial contents
    info_frame = Tkinter.Frame(root)
    info_frame.pack(fill=Tkinter.X, side=Tkinter.BOTTOM)

    # first put the column labels in a sub-frame
    LEFT, Label = Tkinter.LEFT, Tkinter.Label   # shortcut names
    label_line = Tkinter.Frame(info_frame, relief=Tkinter.RAISED, borderwidth=1)
    label_line.pack(side=Tkinter.TOP, padx=2, pady=1)
    Label(label_line, text="Run #", width=5).pack(side=LEFT)
    Label(label_line, text="Source:", width=20).pack(side=LEFT)
    Label(label_line, text="Target:", width=20).pack(side=LEFT)
    Label(label_line, text="Type:", width=20).pack(side=LEFT)
    Label(label_line, text="Proxy Mode:", width=20).pack(side=LEFT)

    # then put the "next run" information in a sub-frame
    info_line = Tkinter.Frame(info_frame)
    info_line.pack(side=Tkinter.TOP, padx=2, pady=1)
    update_specs()

    #-- Finally, let's actually do all that stuff created above
    root.mainloop()

There are a number of things to note in our simple main() function:

  1. Every widget has a parent. Whenever we create a widget, the first argument to the instance creation is the parent of the new widget.
  2. If there are any other widget creation arguments, they are passed by name. This Python feature gives us lots of flexibility in specifying options or allowing them to default.
  3. A number of widget instances (Frame) are global variables. We could make these local by passing them from function to function in order to maintain a theoretical purity of scope, but it would be much more trouble than it's worth. Besides, making these basic UI elements global underlines the fact that they are useful in all of our functions. But be sure to use a good naming convention for your own global variables. (As a forewarning, Pythonists seem to hate Hungarian notation.)
  4. After we create a widget, we call a geometry manager method to let TK know where to put it. A lot of magic goes into TK's calculation of the details, especially when windows are resized or when widgets are added dynamically. But in any case we need to let TK know which set of incantations to use.

Applying geometry managers

TK provides three geometry managers: .pack(), .grid() and .place(). Only the first two are used by tk_txt2html, although .place() can be used for fine-grained (in other words, very complicated) control. Most of the time you'll use .pack().

You're certainly allowed to call the .pack() method without arguments. But if you do that, you can count on the widget winding up somewhere on your display and you'll probably want to give .pack() some hints. The most important of these will then be the side argument. Possible values are LEFT, RIGHT, TOP, and BOTTOM (note that these are variables in the Tkinter namespace).

A lot of the magic of .pack() comes from the fact that widgets can be nested. In particular, the Frame widget does little more than act as a container for other widgets (on occasion it shows borders of various types). So it's particularly handy to pack several frames in the desired orientations and then add other widgets within each frame. Frames (and other widgets) are packed in the order their .pack() methods are called. So if two widgets both ask for side=TOP, it's first come, first served.

tk_txt2html also plays a bit with .grid(). The grid geometry manager overlays a parent widget with invisible graph-paper lines. When a widget calls .grid(row=3, column=4), it's requesting of its parent that it be placed on the third row and on the fourth column. The parent's total rows and columns are computed by looking at the requests made by all its children.

Don't forget to apply a geometry manager to your own widgets, lest you have the rude awakening of not seeing them on your display.


Menus

By itself this line might mystify as much as it clarifies. Most of the work that must be done lives in the functions called *_menu(). Let's look at the simplest one.


Creating a drop-down menu

def help_menu():
    help_btn = Tkinter.Menubutton(menu_frame, text='Help', underline=0)
    help_btn.pack(side=Tkinter.LEFT, padx="2m")
    help_btn.menu = Tkinter.Menu(help_btn)
    help_btn.menu.add_command(label="How To", underline=0, command=HowTo)
    help_btn.menu.add_command(label="About", underline=0, command=About)
    help_btn['menu'] = help_btn.menu
    return help_btn

A drop-down menu is a Menubutton widget with a Menu widget as a child. The Menubutton is .pack()'d to the appropriate location (or .grid()'d, etc.). And the Menu widget has items added with the .add_command() method. (Note the odd assignment to the Menubutton's dictionary above. Don't question this, just blindly follow me here and do the same thing in your own code.)


Getting user input

The example we're going to look at now shows how the Label widget displays output (see Resources for the full source for some examples of the Text and Message widgets). The basic widget for field input is Entry. It's simple to use, but the technique might be a bit different from what you might expect if you've used Python's raw_input() or curses' .getstr() before. TK's Entry widget does not return a value that can be assigned. It instead populates the field object by taking an argument. The following function, for example, allows the user to specify an input file.


Receiving user field input

def GetSource():
    get_window = Tkinter.Toplevel(root)
    get_window.title('Source File?')
    Tkinter.Entry(get_window, width=30,
                  textvariable=source).pack()
    Tkinter.Button(get_window, text="Change",
                   command=lambda: update_specs()).pack()

There are a few things to notice at this point. We've created a new Toplevel widget and a dialog box for this input, and we've specified the input field by creating an Entry widget with a textvariable argument. But wait, there's more!

textvariableinit_vars()main()

source = Tkinter.StringVar()
source.set('txt2html.txt')

This creates an object suitable for taking user input and gives it an initial value. This object is modified immediately every time a change is made within an Entry widget that links to it. The change occurs for every keystroke within the Entry widget in the style of raw_input(), and not just upon termination of a read.

To do something with the value entered by the user, we use the .get() method of our StringVar instance. For example: source_string = source.get()


Wrapup

The techniques we outlined here, along with the ones we used in the full application source code, should get you started with Tkinter programming. After you play with it a bit you'll find that it's not hard to work with. One nice thing is that the TK library may be accessed by many languages other than Python, so what you learn using Python's Tkinter module is mostly transferable to other languages.


Resources

About the author

David Mertz

David Mertz writes many apocopetic articles. He can be reached at mertz@gnosis.cx; his life may be pored over at the Gnosis Web site. Suggestions and recommendations on this, past, or future columns are always welcome.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Linux, Open source
ArticleID=11064
ArticleTitle=Charming Python: TK programming in Python
publish-date=12012000
author1-email=mertz@gnosis.cx
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers