- Installing Tkinter
- Starting and using Tkinter
- Understanding the principles of Tk
- Writing a simple Tkinter application
- Creating and placing widgets
- Alternatives to Tkinter
This chapter, an introduction to programming GUIs in Python, will do two things. First, it will provide a look at the GUI package that comes with Python, taking into account things like its ease of use, the capabilities of the package, cross-platform portability, and so forth. Second, it will give a brief overview of what else is available for GUI programming with Python and how to find it.
The Python core language has no built-in support for GUIs. It’s a pure programming language, like C, Perl, or Pascal. As such, any support for GUIs must come from libraries external to Python, and many such libraries have been developed.
Of all the GUI packages currently available to Python programmers, Tkinter is the one commonly used. Tkinter is an object-oriented layer on top of the Tcl/Tk graphics libraries. The code that drives it is stable, efficient, and well supported. Although it has been knocked for its somewhat plain appearance, in Python 3.1 Tkinter adds support for the new ttk widgets, which greatly improve its look and feel. I feel that it’s a good choice for developing GUIs in Python for several reasons:
- It’s well integrated into Python. Because IDLE uses Tkinter, it’s included in many distributions of Python with no extra installation or can be added easily. That means that you (and your users) don’t have as many dependencies to worry about if you want to distribute your application.
- It’s extremely powerful. Complex GUIs can be coded in a short period of time and with a small amount of code.
- It’s a true cross-platform GUI. When you learn it on any supported platform (currently, Windows, Macintosh, and almost all variants of Linux and UNIX), you can transfer all your knowledge directly to all other supported platforms. If you use the ttk widgets, even the look and feel of the native GUI is supported.
- Tkinter, like Python, is free. You can use it widely throughout your organization without worries about cost.
If you decide that Tkinter isn’t for you, or you already have enough experience with Tkinter to know this, look at the section near the end of this chapter, which covers other possible GUI solutions.
In this section, we’re talking about both the Tk GUI extension to the Tcl language and Tkinter, the Python library that uses Tk and wraps the Tk interface in Python classes. In general, we’ll refer to Tkinter, because that’s all that a Python programmer will usually touch directly. Sometimes we’ll refer to Tk directly, particularly when talking about general features of the underlying Tk library.
Tkinter contains a huge number of features. The following sections aren’t a lesson on how to use Tkinter but more of an introductory overview, primarily aimed at readers who aren’t familiar with Tkinter and want to know if it’s worth their while to look further into it. Remember, for every feature I mention, there are dozens more that I don’t. The Tk command reference alone is almost 300 pages, and that doesn’t cover any of the basic concepts.
If you’re using IDLE, you already have Tkinter installed. On Windows and Mac OS X, Tkinter comes as part of the Python distribution. Some Linux distributions don’t come with Tkinter by default, but they usually have Tkinter packages available. On Ubuntu Linux, for example, installing the IDLE package also installs Tkinter. If you don’t have Tkinter installed, or are having problems making it run, go to the Python home page and search for “tkinter” to find the link to the Tkinter page, which contains documentation, tutorials, troubleshooting information, and instructions on how download the latest version for your platform. Don’t be confused by the fact that you’ll be installing something probably called Tcl/Tk, followed by some version number. Tcl is a scripting language, and Tk is a GUI extension. Python uses Tcl to access Tk, but in a transparent fashion; you’ll never need to worry about the Tcl aspect of the package.
After you’ve installed Tkinter on your system, check to make sure everything is working properly. Start up Python and type
from tkinter import *
If you receive another Python command prompt >>> and no errors, then everything is working okay, and Tk has been started automatically by the importation of Tkinter.
If you’d like to see a brief example of Tkinter in action, the following code creates a dialog box like the one in figure 16.1:
win = Tk()
button = Button(win, text="Goodbye", command=sys.exit)
Note that if you’re in IDLE, you may need to omit the last line. See the sidebar on mainloop() and IDLE.
If you want to run the Tkinter examples in this chapter from within IDLE, you need to be aware that IDLE has two modes: one that allows modules to be run in subprocesses and one that doesn’t. If your version of IDLE runs in the subprocess mode, which is the default on Windows and Mac OS X, you can run the code in this chapter as is, with the mainloop() line included. You should also leave that line in if you’re using the command line or running from the emacs mode.
The no-subprocess mode can be recognized by the ==== No Subprocess ==== message that appears in the shell window when IDLE starts; this is the default in some Linux distributions, such as Ubuntu 9.10. If you run IDLE from a command line, the no-subprocess mode is triggered by adding the –n parameter. If IDLE is running in no-subprocess mode, omit the final line containing mainloop(), or put a # in front of it to make it a comment. In no-subprocess mode, IDLE is already running a mainloop under Tk, and running a second mainloop may cause the whole IDLE process to hang.
When you click the Goodbye button, the sys.exit command is executed, and Python quits. This window is only a little larger than the button it contains and may appear behind another window. But as long as you didn’t get any error messages, it should be there.
The Tkinter GUI package is based on a small number of basic principles and ideas, and this is the main reason it’s relatively easy to learn and use. Although it certainly helps to have some previous knowledge of GUI-based programming, this isn’t strictly necessary. Tkinter is a relatively easy way for you to learn GUI and event-driven programming.
The first basic idea behind Tkinter is the concept of a widget, which is short for window gadget. A widget is a data structure that also has a visible, onscreen representation. When the program changes the internal data structure of the widget, that change is automatically displayed on the screen. Various user actions on the visible representation of the widget (mouse clicks and so forth) can, in turn, cause internal changes or actions within the widget’s data structure.
Tkinter is a collection of widget definitions, together with commands for operating on them, and a few extra commands that don’t apply to any specific widget but that are still relevant to GUI programming. In Python, each different type of widget is represented by a different Python class. A Button widget is of the Button class, a Label widget is of the Label class, and so forth.
This direct mapping between Tkinter widget types and Python classes makes using widgets in a Python program extremely simple. For example, a Python program that creates and uses a Button widget and a Label widget looks something like this:
from tkinter import *
my_button = Button(...optional arguments...)
my_label = Label(...optional arguments...)
This style of mapping Python classes to widgets is common in Python GUI environments, although the exact names of the widgets and their parameters will naturally vary.
The second basic idea behind Tkinter is the availability and use of named attributes to fine-tune widget behavior. To understand why this is necessary and see how useful it is, let’s look at an apparently simple task—creating a button.
The simplest way to do this is to specify a class, say AButton, with a one-argument object constructor, whose single argument is a string that will become the name displayed on the button. Creating a button looks like this:
my_button = AButton(name)
But this provides no way to associate a command with the button—that is, the name of a function that should be executed when the user clicks the button. To do this, change AButton to have a two-argument constructor, with the command as the second argument:
my_button = AButton(name, command)
Sometimes, though, we’ll want our button to stand out; maybe instead of black text on a gray background, we want red text on a green background. This necessitates giving even more arguments to the AButton constructor:
my_button = AButton(name, command, foreground_color,
Even this isn’t enough. We may want to have buttons with thicker borders, or buttons with specific heights or widths, or buttons that contain a small picture instead of text. We could easily require an AButton command with 20 different arguments, with the end result being that the AButton command would be practically unusable and wouldn’t give us the control we want.
Tkinter solves this problem by specifying almost all properties of widgets as named attributes, values that can optionally be given by name when the widget is created and that can be accessed or modified by that same name later in the life of the widget. This works well with Python’s named parameter passing, making it easy to create widgets with the desired attributes. For example, the name of the attribute that defines the string displayed in a button is text. The Python command to create a button that displays the string "Hello!" is
my_button = Button(text="Hello!")
Most attributes of a widget have a default value, which is used when you don’t supply a value for that attribute. Generally speaking, these defaults make sense for the common case, and most of the time most named attributes can be ignored. For example, the named attribute that controls the color of the text in a button is called foreground, and its default value is the string "black", which causes the button text to display as black. To override this default, give a specific value for the foreground attribute:
my_button = Button(text="Hello!", foreground="red")")
Named attributes are used extensively throughout the Tkinter widget set, and many attributes can be used with almost all widgets. The use of named attributes greatly simplifies the process of GUI programming and makes code more readable.
The final basic aspect of Tkinter that you need to understand is the idea of geometry management, meaning how widgets are placed on the screen. It isn’t obvious in the previous example, but typing in a line of Python code like so
my_button = Button(text="Alright!")
isn’t enough to display the button onscreen. Tk doesn’t know where you want the button to show up, and until it’s told the desired position, it will keep the button hidden. Deciding where to display the button onscreen is a function of the window hierarchy and associated Tk geometry managers.
To understand the idea of the Tk window hierarchy, you need to know about two special Tk widget classes, called Toplevel and Frame. Both Toplevel widgets and Frame widgets may contain other Tk widgets (including other frames) and are the basic building blocks for constructing complex GUIs. A Frame is a container for other widgets and can be either the main window of an application or contained in another frame. Nesting frames within frames can be useful in laying out and grouping widgets.
Tk uses a Toplevel widget to represent a complete window in a GUI, complete with title bar, close and zooming buttons, and so forth. A Toplevel widget is useful for custom dialog boxes and other situations where you want a window that’s independent from the main Frame.
The subwidgets contained in any particular frame are arranged for display according to one of Tkinter’s three built-in geometry managers. These managers permit you to specify the arrangement of the subwidgets in various ways, ranging from giving exact coordinates for each widget within a window to giving only relative placement, leaving the precise sizing of each widget to the geometry manager.
For the purposes of talking about Tkinter in this chapter, we’ll refer to only one geometry manager: the grid manager, which is the most powerful. grid works by placing all widgets in an implicit grid, similar to a spreadsheet layout. If you start using Tkinter, you’ll want to learn the pack and place geometry managers as well, which you can use to specify that widgets should be placed relative to one another (pack), or in absolute locations in a window (place).
Let’s start with an example that introduces all of the basic Tkinter concepts: window hierarchies, geometry management, Tkinter attributes, and a couple of the most basic widgets. The example is a simple one. When run, it produces a window that resembles figure 16.2. It may look different on your machine, because Tk provides a native look and feel for whatever operating system it’s running on. This example was produced under the Windows XP operating system.
Clicking the Increment button adds 1 to the number shown in the Count field, and clicking the Quit button quits the application.
Listing 16.1 contains the code to do this.
- Widget creation, accomplished here by the Label and Button commands— Tkinter lets you create many different types of widgets, such as lists, scroll bars, dialog boxes, radio and check buttons, and so on.
- Widget placement, accomplished in this case by the grid command — Tk provides a great deal of control over how widgets are placed and sized. grid will be discussed in more detail in a later section.
- The use of widget attributes, to set and modify the appearance and behavior of widgets — The widget attributes used in the example are the text attribute, which controls the text displayed by a widget, and the command attribute, which sets the function the widget will execute when it’s clicked. You can set widget attributes when a widget is first created and change them after a widget has been created by using the configure widget method.
- A basic window hierarchy created by the program — The main_window is the top-level window widget. It, in turn, contains the count_label, incr_button, and quit_button widgets.
You create widgets in Python by instantiating an object of that widget’s class, using the name of the type of widget being created. Button and Label widgets were created in the previous example, but you can also create Menu, Scrollbar, Listbox, Text, and many other types.
The widget-creation commands all follow the same general form. They all have one mandatory argument, the parent window (or parent widget), followed by zero or more optional named widget attributes, which determine the precise appearance and behavior of the new widget. Each creation command returns the new widget as a result. You’ll usually want to store this new widget somewhere so that you can modify it later if necessary. A line in a Python program that creates a widget usually looks something like this:
new_widget = WidgetCreationCommand(parent, attribute1=value1,
attribute2=value2, . . .)
The parent window of a widget called w is the window (or widget) that contains w. It’s important to define a parent for several reasons. First, widgets are always displayed inside and relative to their parent window. Second, Tk provides a powerful event mechanism (which we don’t have space to discuss), and a widget may pass events to its parent if it can’t handle them itself. Finally, Tk can have widgets that act as windows, in that they themselves are the parent window for (and contain) other widgets, and the widget-creation commands need to be able to set up this sort of relationship.
1 This is an oversimplification. The parent window of a widget is generally the widget that contains that widget. But this isn’t strictly necessary.
All the other optional arguments in a widget-creation command define widget attributes and control different aspects of the widget. Some widget attributes are common to several different widget types (for example, the text attribute applies to all types of simple widgets that can display a label or a line of text of some sort), whereas other attributes are unique to certain widgets. One characteristic that makes Tk special, compared to other GUI packages, is that it gives you a great deal of control over your widgets. There are many attributes for each widget. To give you an idea of this, here’s a program that uses some of the attributes that control the appearance of widgets. Figure 16.3 is the resulting window (in black and white, unfortunately):
from tkinter import *
main_window = Tk()
label = Label(main_window, text="Hello", background='white',
foreground='red', font='Times 20',
Most attributes of widgets can be set at creation in this way.
Creating a widget doesn’t automatically draw it on the screen. Before this can be done, Tk needs to know where the widget should be drawn. The grid command was used in the previous example and will be discussed in detail.
Tk is more sophisticated in the way it handles widget placement than most GUI packages. Under Windows, the standard way of setting the locations of widgets is to specify an absolute position in their parent window. This can also be done in Tkinter (using the place rather than the grid command) but usually isn’t, because this technique isn’t very flexible. For instance, if you set up a window for use on a monitor that has 640×480 resolution, and a user uses it on a monitor that has 1600×1200 resolution, the window uses only a small amount of the available screen space and can’t be resized (unless you write the code to resize the window). This is a common problem with many programs.
Instead, Tkinter usually makes use of the notion of relative placement, where widgets are placed in such a manner that their positions relative to one another are maintained no matter what size the enclosing window happens to be. This can get complex. For example, you can specify that widget A should be to the left of widget B, and that when the enclosing window is resized, widget A should grow to take advantage of the extra space, but widget B shouldn’t. We won’t get into all of the possibilities but will attempt to present enough of the features of Tk widget placement to illustrate the ease and power of the methods it uses.
The grid command places widgets in a window by considering a window as an infinite grid of cells. You place a widget into this grid by specifying row and column arguments to the grid command, which tell it in which cell to place the widget. The rows and columns will expand as needed to display the widgets they contain, and any rows or columns that don’t display any widgets aren’t displayed.
As a simple example, we’ll put two buttons in the corners of a 2?×?2 grid:
from tkinter import *
win = Tk()
button1 = Button(win, text="one")
button2 = Button(win, text="two")
When run, this program produces a window that looks like figure 16.4.
The cells of the grid are automatically sized large enough to display what they contain, although you can override this and place constraints on the maximum sizes of the cells.
This makes it easy to set up a text window with scrollbars and, with the proper placement (see figure 16.5), treat the window as a 2×?2 grid into which the Text and Scrollbar widgets will be placed.
The program to do this is shown in listing 16.2.
from tkinter import *
main = Tk()
text = Text(main)
text.grid(row=0, column=0, sticky='nesw')
vertical_scroller = Scrollbar(main, orient='vertical')
vertical_scroller.grid(row=0, column=1, sticky='ns')
horizontal_scroller = Scrollbar(main, orient='horizontal')
horizontal_scroller.grid(row=1, column=0, sticky='ew')
The commands in the code ensure that any extra space given to the grid as a result of resizing the top-level window is allocated to column 0, row 0—that is, to the Text widget. The resulting window is shown in figure 16.6.
In addition to the three grid method invocations that place the text box and two scrollbars in their appropriate cells, this code reveals new aspects of Tk, which build on the fundamentals of Tk to provide further control over the user interface:
- The orient attributes of the Scrollbar widgets control whether a scrollbar scrolls vertically or horizontally.
- The sticky attributes of all three widgets control how they’re placed in their cells. For example, the Text widget has a sticky value of 'nesw', which means that its north (top) side should stick to the north side of the cell it’s in, its east (right) side should stick to the east side of its containing cell, and similarly for the south and west sides. The Text widget should completely fill its cell, which means that if its cell grows, the text widget should also grow and automatically reformat the text it contains to take advantage of the extra space. It’s then fairly easy to guess that the sticky value of 'ns' for the vertical scrollbar means it should always stretch in the vertical direction to fill its cell (and always be the same height as the Text widget), and analogously for 'ew' and the horizontal scrollbar.
- The columnconfigure command specifies that if the window containing the entire grid of widgets is expanded in the horizontal direction, all the extra space resulting from the resizing should be allocated to column 0, the column containing the Text widget. rowconfigure specifies similarly in the vertical direction. Together, columnconfigure and rowconfigure ensure that if the top level is resized to be larger, then the extra space resulting from that resizing should be given to the Text widget, which is generally the desired behavior.
That’s a lot of GUI detail specification in a small amount of space. But that’s exactly the point of Tk—to enable you to rapidly specify your GUI, right down to the details. The fact that settings are accomplished through easy-to-remember keywords, rather than a multitude of binary flags, doesn’t hurt either.
One problem with creating Tkinter applications as we have so far is that they quickly become hard to read and maintain as you add more widgets and code. Using the OOP principles introduced in the previous chapter and making an application class that inherits from Frame can make your code much more organized and easy to read and maintain. Listing 16.3 shows the previous counter application, refactored as a class.
This code is no longer than the previous version, but it’s much better organized and easier to read. Basic initialization, widget setup, and counter incrementing are now much easier to pick out; and because increment_counter is now an instance variable, we no longer need to make it global. Even though you almost certainly won’t want more than one instance of Application at a time, creating the class is worth it because of the improved organization (and readability and maintainability) it gives your code. Use this pattern for your Tkinter applications—it will more than pay off.
The previous examples don’t come close to exploring the capabilities of Tkinter, but they should give you some idea of what it feels like. There’s no way in the space of one chapter to illustrate by example all the facilities Tk provides. The next few sections will cover the remainder of Tkinter’s abilities on a much higher level. If you’re familiar with GUI programming, you’ll be at home with most of what is discussed.
If you aren’t familiar with GUI programming, the examples so far are a good starting point for learning by experimentation. They should give you grounding in Tk’s basic philosophy and concepts. You can find further detailed information linked on the Tkinter page on the Python website. Also be sure to check out John Grayson’s comprehensive book Python and Tkinter Programming (Manning, 2000).
Event handling—how a GUI library handles user actions such as mouse movements or clicks or keypresses—is a critical part of GUI programming. An awkward event-handling scheme can make your GUI development a real nightmare, whereas a good event-handling mechanism can make your task far more pleasant. Tk event handling, although not perfect, is up near the top. One of the big questions with event handling is event direction—if you press a key on the keyboard, which text entry box or text widget (or other widget) of the many in your interface is that directed to? Many GUI libraries require a complete specification of how events are to be directed; you must say, “If this widget can’t handle a mouse click event, it should pass that event over to this other widget, and so on.” You can do this in Tk, but you aren’t required to. Tk uses the hierarchical structure of your user interface, together with various commonsense rules, to try to automatically direct events to the appropriate widget. Most of the time, it gets it right.
Tk also provides a rich set of events to choose from. For example, look at the problem of changing the mouse cursor to a paintbrush, as the mouse enters the main paint window of a painting program. One way to do this would be to continually (explicitly) monitor the position of the mouse relative to the main paint window and manually adjust the cursor as necessary. This would be necessary if only basic mouse-related events, such as movements, were reported by and to Tk. But Tk provides a higher-level event for all windows, subwindows, and widgets, called the <Enter> event. An <Enter> event for a window or widget is generated every time the mouse cursor enters that window or widget (and a corresponding <Leave> event is generated when the mouse leaves). A built-in bind command makes it easy to bind <Enter> events for a particular window, such as a painting window, to specific functions, such as a function to change the mouse cursor to a paintbrush.
If the built-in set of Tk events isn’t enough, you can define or generate your own virtual events. These are events defined by you, just as you can define application-specific functions. For example, you might wish to define the virtual event called <<Copy>> and set it to be equivalent to the keyboard event generated by pressing Ctrl-C. If you ever migrated your program to a platform or language (such as Chinese) where Ctrl-C wasn’t an appropriate key binding for a <<Copy>> event, you could redefine <<Copy>> in terms of another keystroke—but all references to <<Copy>> in your code would remain valid, without needing any changes. Generating virtual events is also a convenient way to distribute events widely throughout your application without worrying about plumbing details. For example, you could define a button called MyButton, such that clicking this button would generate a <<MyButtonPressed>> event. Any other widgets could then be instructed to listen for <<MyButtonPressed>> events without referencing the original button—indeed, without knowing or caring that a button was the source of the event.
Two widgets in the Tk widget set deserve special mention, because they provide abilities reflecting literally years of implementation effort. These are the Canvas and Text widgets, which respectively provide high-level manipulation capabilities for object-oriented graphics and for text. Both widget types are an order of magnitude more advanced than analogs found in most other GUI libraries.
The Text widget supports all the basic functionality necessary for a basic WYSIWYG word processor, including font families, styles, and sizes; a rich set of default key bindings; automatic controllable line wrapping; settable tabs; the ability to embed images or other widgets onto the text drawing surface; and so forth. In addition, you can tag text within the widget with any number of user-chosen strings and then manipulate the text via those tags. You could, for example, define a tag called bold to be applied to all text in your widget that should be displayed in boldface. A single line of code would then let you change the display style for all bold text from Helvetica 10 pt. bold to Times Roman 14 pt. bold, or, if you prefer, to red text on a green background with a 2-pixel-wide raised border. You can easily define sections of text that highlight as the mouse cursor passes over them and that cause some action when they’re clicked, mimicking the effect of buttons or hypertext. Many other abilities also come with the Text widget.
The Canvas widget is similar, particularly with respect to the use of tags. You can associate arbitrary tags with an object and manipulate as a whole all objects on the canvas that have a given tag. For example, it’s an easy matter to define a complex shape by drawing a series of lines, curves, and other shapes, all with the same programmer-defined tag, and then to move that shape around the canvas as a unit, by instructing the Canvas widget to move all simple shapes having the given tag. It’s also easy to use shapes as buttons, to set various aspects of their appearance (such as foreground and background colors and line width), to define layering of shapes atop one another, and to perform many other advanced tasks that would take months of effort if undertaken from scratch.
Tkinter may not satisfy your needs. It isn’t particularly fast and isn’t a good candidate for games or for image-manipulation programs. Its high-level approach means that particularly unusual or specialized user interfaces may be difficult to implement. Or, you may not have the time to learn Tkinter. Fortunately, alternatives are available.
Three cross-platform windowing/GUI libraries stand out, being available for at least Windows, Mac OS X, and Linux/UNIX. The first is the Qt package, which forms the basis for the well-done KDE desktop environment effort, a large project geared toward producing an integrated and comprehensive desktop environment for Linux/UNIX and compatible operating systems. QT is a rich and powerful GUI framework and also has QT Designer, a capable GUI builder. You can find out more about Qt at http://qt.nokia.com. Also, www.kde.org will give you a chance to browse screenshots of many applications built using KDE.
The next cross-platform GUI option is GTK. This is the GIMP Toolkit and is the basis for the GNOME desktop environment project, which is comparable to KDE. GTK is similar to Qt in scope and capability, and it has also been ported from Linux/UNIX to work on Windows and Mac OS X. Another interesting option with GTK is Glade, a graphical tool to build GUI interfaces for GTK. Glade saves its files in XML format, which can be used directly with a library called libglade or used to generate the appropriate code. A good starting point for finding out about GTK is the GTK website, at www.gtk.org.
Finally, there is the wxPython toolkit, based on the wxWidgets framework. wxWidgets was created to be portable across Windows and UNIX platforms and has since been extended to Mac OS X and other platforms. wxPython is a strong framework, offering a native look and feel on different platforms, and is widely used. Visit www.wxpython.org to find out more about wxPython.
One thing to keep in mind about all of these GUI options is that they require you to install both the GUI libraries themselves and the Python libraries to use them. This means more work on the user end of things and can lead to issues with making sure the right versions of all the dependencies are installed. On the other hand, developers in many situations believe the tradeoff is more than worth it.
Many other GUI libraries are available. Good descriptions and evaluations are available on the Python wiki’s Gui Programming page, which you can find by visiting the main python.org website and searching for “gui programming.”
Python ships with a comprehensive and well-thought-out interface (a Python module) called Tkinter, which allows access to the freely available and powerful Tk user interface library. Tkinter is a handy framework for a scripting-style language, in that it allows rapid interface development, interactive execution, and runtime control over all aspects of the interface, with powerful abilities. It suffers some of the same drawbacks of scripting languages, particularly execution overhead that may be too high for graphics-intense applications, and inflexibility in the UI model it uses.
There are many alternatives. If you’re programming only for the Windows environment, you can take advantage of your MS UI library knowledge through direct calls to the Microsoft APIs. Qt, GTK, and wxPython are all excellent cross-platform choices.