Part I: Graphical User Interfaces

Interface programming and Tk

Objects

All modern graphical user interface (GUI) toolkits, including the Tk package used in Python, are based on an object-oriented model of the user interface. Typical object types are windows, entry fields, buttons, text fields, graphics fields, and menus. Constructing a GUI means creating all the necessary objects, assembling them correctly, and specifying the actions associated with them.

Event-driven computing

In traditional (procedural) programs, the program flow is determined by the code: the main thread of execution is controlled sequentially by each line in your code. With a GUI, however, it is the user who—by activating menus, buttons, and other input objects—determines what happens and in what order. This requires a different program structure, one that responds to the events generated by the user. In particular, largely hidden from view and user control, there is a main loop that repeatedly checks for user input and, when detected, calls appropriate subroutines that do the indicated work. The main loop is terminated only when the user decides to quit the program.

User interface design

Here we can cover only the mechanical aspects of creating a GUI. An equally important question is the design of a user interface, that is, deciding what to put where and how. There is an extensive literature on this subject, but the most important recommendation is to study well written programs and try to imitate their behavior. Also, the books by Tufte, in the supplementary reading list, contain useful insights in how viewers take in and parse graphical information.

The most common beginner's mistake is trying to be too innovative. The goal is not to create a GUI that packs the most features into a single window, using tricks specific to the application. Rather it is to design a GUI that users can understand quickly, based on their experience with other programs. A GUI is an attempt to represent the current state or configuration of a running system in a way that is intuitive and that the user can anticipate.

Tk and Python

Python does not have a user interface package of its own, but there are Python interfaces to several such packages. The most popular one is Tk, which was originally developed for Tcl, a simple scripting language. Tk has several advantages: it is easy to use, complete and available for all major operating systems. However, it is rather closely tied to Tcl and, although the Python interface tries to hide this as much as possible, there remain some features that don't seem to make sense with Python. An interface toolkit designed for Python from the start would probably look different.

For example, although the following covers the Tk GUI toolkit, which comes standard in the Python distribution, there are alternatives, such as wxPython. Once you're familiar with Tk programming and basic GUI design, I recommend that you explore using wxPython. It's a more modern and Python-savvy GUI toolkit than Tk.

Tk's world view

Tk knows about two types of object: windows and widgets. Widgets are the user interface objects that populate windows: buttons, text fields, sliders, and the like. Widgets are arranged in a hierarchical order. Every widget has a master—another widget or, for the outermost widget, a window. A widget's position on the screen and many of its properties depend on its master.

Windows are part of a hierarchy as well, but of a different kind. A window's position and content are completely independent of that of its master, but it disappears when its master is closed. The top of the window hierarchy is the root window, which has no master. When the root window is closed, the Tk event main loop is terminated.

A typical Tk application starts by constructing a widget that is attached to the root window and that runs its main loop. Widgets specify functions to be called in reaction to events (user actions). These functions are called by the Tk main loop when the events occur. These functions can do anything at all; there are no restrictions. They can open and close windows and even modify the widgets in existing windows. They can also ask Tk to terminate its main loop.

After a widget has been created, its creator must decide where to place it within its master. Tk provides two strategies for placing widgets: the placer provides a very flexible, but also very complicated, strategy. Whereas the packer is simpler to use and does most of the work by itself, at the price of some loss of flexibility. Most Tk programs use only the packer, as will all examples here.

A very simple example

The following program is about as simple as a Tk application can get. It opens a window containing only a button. When the button is pressed, a message is printed to the screen. The program is terminated when the window is closed.
from Tkinter import *

def report():
    print "The button has been pressed"

object = Button(None, text = 'A trivial example', command = report)
object.pack()
object.mainloop()

To try this use this SimpleTkExample.py code.

The first statement imports all names from the module Tkinter. Due to the large number of definitions in this module, an individual import is rather inconvenient, but we won't concern ourselves with optimization here.

Then the event action function report() is defined. This can be any Python function.

The next line creates the button widget. Its first parameter is the widget's master; None indicates the root window. The remaining keyword parameters specify the details of the button: the text it displays and the command to be executed when the button is pressed.

Then the widget's pack() method is called, which indicates that the packer is to be used for arranging the widget. There are no indications on how to arrange the objects, because there is just one widget and thus no choice.

Finally, the main loop is started. This passes control the Tk's event monitoring subsystem.

Run the example code. Click on its button. What happens each time you do?

Arranging widgets

A Tk window can contain only one widget. Of course one-widget windows are not very useful, so there is a way to construct more complicated ones: the use of frames. A frame is a widget that acts like a subwindow. It is normally invisible (although a visible border can be added), but it contains other widgets that usually are visible. A frame can contain any number of widgets of any kind.

If a frame contains several widgets, there must be some specification of how to arrange them. This information is supplied by optional keyword arguments to the method pack. The simplest specification is side=some_side, with the choice of TOP, BOTTOM, LEFT and RIGHT. The packer then tries to place the widget as close as possible to the indicated side of the frame, within the constraints imposed by other widgets. If, for example, you specify side=TOP for all widgets in a frame, they will be stacked up vertically and centered horizontally, with the first widget for which pack was called at the top.

More complicated constructions can be made by using frames within frames. The packer specifications for any widget affect its arrangement within the surrounding frame, which then has its own packer options for placing it within another frame.

The following example shows the use of frames to construct a window containing four buttons in two centered lines:
from Tkinter import *

def report():
    print "A button has been pressed"

outer_frame = Frame(None)
outer_frame.pack()

first_line = Frame(outer_frame)
first_line.pack(side=TOP)
button1 = Button(first_line, text='Button 1', command=report)
button1.pack(side=LEFT)
button2 = Button(first_line, text='Button 2', command=report)
button2.pack(side=LEFT)

second_line = Frame(outer_frame)
second_line.pack(side=TOP)
button3 = Button(second_line, text='Button 3', command=report)
button3.pack(side=LEFT)
button4 = Button(second_line, text='Button 4', command=report)
button4.pack(side=LEFT)

outer_frame.mainloop()

To try this use this TkFramesExample.py code.

Note that the first frame has master None, meaning the root window. The inner frames first_line and second_line, each representing a line of buttons, have the outer frame as master. The buttons, then, are attached to these inner frames.

As before, the outermost frame's event loop, outer_frame.mainloop(), is called, passing control to Tk.

How would you modify the example code so that each button click produced a different message?

Rolling your own widgets

Any GUI can be built up like the last example, by constructing it object by object and providing the packer options. However, such a program quickly becomes large and unreadable. Modifying it will be next to impossible. The GUI objects have to be structured in some way, and the usual way is the definition of application-specific higher-level widgets. The example above, for example, would benefit from a “line of buttons” widget.

Widgets are defined by subclassing existing widgets, which can be either standard Tk Widgets or other application-defined widgets. Almost all higher-level widgets are subclasses of Frame. Their initialization method creates all the widgets within the frame and specifies their arrangement. Such widgets can then be used as if they were standard widgets.

With a “button line” widget, the four-button example can be rewritten as shown in YourOwnGUIWidgets.py.
# Simplifying the previous example,
#        by subclassing to make a line of buttons widget

from Tkinter import *

def report():
	print "A button has been pressed"

class ButtonLine(Frame):

	def __init__(self, master, button_data):
		Frame.__init__(self, master)
		for text, command in button_data:
			Button(self, text=text, command=command).pack(side=LEFT)

outer_frame = Frame(None)
outer_frame.pack()
first_line = ButtonLine(outer_frame, [('Button 1', report), ('Button 2', report)])
first_line.pack(side=TOP)
second_line = ButtonLine(outer_frame, [('Button 3', report), ('Button 4', report)])
second_line.pack(side=TOP)

outer_frame.mainloop()

Tk widgets: an overview

It is impossible to describe all Tk widgets here in detail, but an overview of the widgets and their most common application is a good start for further exploration in the Tk manual.

There are also various image objects to display drawings, photos, and the like.

For more information about these widgets, see the Tkinter documentation here and here.

A not-so-simple example

This program displays a small window where the user types a filename and then either displays the text in a window or prints it. Instead of typing the filename, a file browser can be used. Here's a screen snapshot showing the main window, a text window, and the file browser.

[screen snapshot]

The program uses three widgets that are not standard Tk widgets, but part of the Python standard library: a dialog box for displaying error messages, a file browser, and a text widget with scroll bars. The program also defines two widgets of its own: a "filename entry" widget, consisting of a label, an entry field for the filename, and a button to run the file browser, and a "button bar" that contains a group of left-aligned buttons and a group of right-aligned buttons. It also defines the two full-window widgets by classes, one for the text display window, and one for the main window.

The program uses one packer option that has not been mentioned yet. Normally all widgets have a fixed size, depending on their contents. For some widgets, however, there is no obvious correct size, e.g. for entry fields. Entry fields get an arbitrary size assigned by the packer. The option fill=X tells the packer to extend the widget in the x direction to fill whatever space is available in the widget's master.

Note that the actions linked to the buttons are defined as methods of the widgets. This is necessary to give the actions access to the attributes of the widget, which they often need. In a real Tk program, almost all actions are defined by widget methods.

Learning more about Tk

The Tkinter documentation provided by Pythonware is almost complete and sufficient for most needs. Start by reading the introduction and then use the reference as you develop your programs. This introduction appears more complete, however.


Table of Contents