Charming Python: TK programming in Python

Tips for beginners using Python's GUI library

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

David Mertz, President, Gnosis Software, Inc.

David MertzDavid 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.



01 December 2000

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.


Learning the basics

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

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

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

Tkinter makes menus painless. Although we're working with a much simpler example here, you can, if you want, populate your menus with different fonts, pictures, checkboxes, and all sorts of fancy child widgets. In our case, the menus for tk_txt2html are all created with the line we saw above.

menu_frame.tk_menuBar(file_menu(), action_menu(), help_menu())

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.

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.

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.

.get()

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

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Linux on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Linux
ArticleID=87928
ArticleTitle=Charming Python: TK programming in Python
publish-date=12012000