The Friendly Python

Lesson 5: Simple GUIs

A Taste Of Programming
Lesson: 1 2 3 4 5

Mississippi College

Computer Science 114

The programs we have written so far are text-based. Each program accepts character data from the keyboard, and produces textual output to the screen. While this can be quite useful, most current computer users are far more familiar with the Graphical User Interface (GUI), which presents buttons and menus which the user can manipulate using the mouse pointer. In this lesson, we'll write a few of these using Python.

An interface is where two things come together and interact; in this case, the program and the human being. The program is still doing the same sorts of things inside. (So, presumably, is the human being.) The difference is the method of interaction. GUIs are used because they are usually easier for humans. For the machine, GUIs require more computational resource, and are generally more difficult to program. But, as Mark Weiser was fond of saying, "Computers should work. People should play." So, off we go to GUI land.

  1. As usual, start Idle.

  2. Now, we'll not just dive right in. First, a bit more on functions. What we told you about function parameters in Lesson 3 was the truth, and nothing but the truth. But not the whole truth. Here comes a bit more of it. Enter and run the following program:

    # Named parameter demo.
    def afunc(snakes = 3, birds = 8, squirrels = 30):
        print 'snakes:', snakes
        print 'birds:', birds
        print 'squirrels:', squirrels
        print

    afunc(19, 3, 23)
    afunc('yes')
    afunc(birds = 12)
    afunc(1, squirrels = 129)

    As you can see, a parameter can be given a default value. When you call the function without an argument for that parameter, the extras take their defaults. It is also possible use the parameter names to specify where to send any particular argument, as demonstrated in the last two calls.

  3. Okay, now we can dig in and get our fingers GUI. Enter the following program and follow the instructions.

    # Simple button demo.
    from Tkinter import *

    # Change the label text
    def changelab():
        lab.configure(text="I TOLD you not to push it!")

    root = Tk()
    lab = Label(root, text="Don't Push The Button!")
    lab.grid(row=0, column=0)
    but = Button(root, text="Push Me", command=changelab)
    but.grid(row=1, column=0)
    root.mainloop()
    Please do the following:
    • Save the program on the computer's desktop. Give it an ending .pyw, not .py.
    • You should see the file on the desktop, with a small green snake icon. If you need to, move your open windows around until you find it.
    • Run your program by double-clicking the icon. It should open a window like the top image at right.
    • Be daring and click the button anyway. The window will change to match the lower one one on the right.
    • Only the first button press does anything, but you can always run the program again.
    • Use the X at the upper right corner of the window to exit the program.
    You can also run the program from Idle as in the previous lessons, though running it like any other Windows program is nicer. However, if you have problems with your program, running it from Idle may make it easier to see any error messages.

  4. This program introduces a number of new concepts, which will need dissecting. In previous lessons, we've seen some Python types: integer, float, string and boolean. Variables can contain data of any type, but what you can do with the variables depends on that type. Integers can be added; strings can be concatenated. You can use square brackets to select a character from a string, but not from an integer. And so forth. None of the variables in this program contain those types of data. The variable root is a window, lab is a label (text that appears in a window), and but is a button. These are all types which are created by the Tkinter library. Collectively, these variables are all known as objects, and they have object types. For our purposes, objects are variables which have some type defined in a library. (Object types can be created in an ordinary program as well, but we won't pursue that.) Just as with basic types, the things you can do with an object depend on the type of the object.

    Here's what the statements do:

    from Tkinter import *

    The first line is an import statement (even though it starts with the word from). We won't be trying to build our own import statements, so we'll skip the details. What it does is tell Python to use the contents of a library called Tkinter. Libraries are collections of functions and other program code which programmers have created and stored in a file. A library is a programmer's toolbox. Each library generally holds tools specialized for a specific task. The Tkinter library provides tools for creating a GUI.

    root = Tk()

    This a call of the zero-argument function Tk which comes from the Tkinter library. Tk() creates an empty window and returns it, where it is stored in the variable root.

    lab = Label(root, text="Don't Push The Button!")

    This adds a label to the empty window root. Again, Label is a function from Tkinter, and it returns the label object which it creates. The Label function takes a parameter to tell what window the label is on, and has many other parameters provided with default values. As you see, we have sent an argument for one of these parameters using its name.

    lab.grid(row=0, col=0)

    This makes the label actually appear on the window, and tells where to put it. Tkinter arranges the parts of the window in a grid,* starting with 0, 0 in the upper left corner. This small example has one column and two rows.

    but = Button(root, text="Push Me", command=changelab)

    but.grid(row=1, col=0)

    This creates the button and makes it appear below the label. The parameter command is a function to call when the button is pressed. The Tkinter package waits for the button press, and calls the specified function for us when that happens.

    root.mainloop()

    This essentially tells the window that you are done constructing your GUI, and the system should activate it. The mainloop function tells the computer's windowing system to display your window, then waits for mouse clicks and acts on them.

    Tkinter refers collectively to the objects which make up the contents of the window, buttons, labels and such, as widgets.

    As you can see, the function changelab is not called anywhere in the program. Instead, we send the name changelab to the button object through the command parameter. When the button is pressed, the button object calls it for us. This is an example of event-driven programming. The function is executed in response to an event (the mouse click) initiated by the user.

    When the program first runs, you see the various widgets you created. When you press the button, the button object calls changelab which modifies the text on the label. You can observe the change in the window. Changelab does its work by running configure on the label object stored in lab. Configure is a function which is part of the button object type, called a method. Methods are defined when a programmer creates an object type; they are part of what you can do with objects of that type. As shown, configure lets you change things about the label widget (its properties), including the text it displays.

    A. Push To Change

    Modify the above example to change the text of the button as well as the label when the label is pushed. Buttons have a similar configure method. You may choose any new text you like.

  5. Here's a more colorful version of the above example. Please modify your program to look like this, and run it again.

    # Simple button demo with extra attributes.
    from Tkinter import *

    # Change the label text.
    def changelab():
        lab.configure(text="I TOLD you not to push it!", bg='red')

    # Stop the program.
    def stop():
        root.quit()

    root = Tk()

    lab = Label(root, text="Don't Push The Button!",
                bg='blue', fg='white', relief='sunken')
    lab.grid(row=0, column=0, columnspan=2)
    but1 = Button(root, text="Push Me", command=changelab, 
                  bg='green', activebackground='#99FF99', relief='groove')
    but1.grid(row=1, column=0, sticky='news')
    but2 = Button(root, text="Exit", command=stop, bg='#FF9999')
    but2.grid(row=1, column=1, sticky='news')
    root.mainloop()
    This program demonstrates a few more Python features, and some of the large number of attributes which Tkinter widgets possess. All the new attributes shown here are cosmetic: they change the appearance, not behavior, of the widget. Here's the scoop:
    • The program uses a feature of Python to continue a statement onto the next line: the statement will not end until you've closed all the parentheses. Only the indenting of the first line matters for grouping.
    • The layout is changed to 2 rows by 2 columns, and the label is now gridded with columnspan=2 so that it will fill the entire first row. (Note that you need to spell out columnspan; colspan won't work.)
    • Colors are set in several places. The bg attribute stands for background, and sets the background color. The fg stands for foreground, and sets the color of the lettering in the button or label. The activebackground option sets the background color when the mouse is over the object. The colors can be specified by name, or numerically as in HTML.
    • The sticky attribute to grid makes the object stick to the listed sides, denoted by compass points. The n for north means the top, the e for east means the right, the w for west means the left, and s for south means the bottom. "Stick to" means that the widget contents will be moved to that side of its grid cell. If the sticky option specifies opposite sides, the contents will be stretched to fill in both directions. The compass points may be listed in any order. The example gives all four points, which causes the widget to fill its grid cell. This is a very common use of sticky.
    • The relief attribute controls how the object is displayed, particularly the shadow used to give an appearance of depth. The default for buttons is raised, by which they appear to protrude from the screen. Other legal values are sunken, flat, ridge, solid and groove. If you can't guess what one of those does, feel free to try it and see.
    • The Exit button calls stop, which calls root.quit(), which does exactly that.

    After you run the program once, take the sticky='news' away from each button, and run it again to see the difference. While you're at it, maybe try some other relief values.

    B. Back In The Day

    Modify the above example to add a reset button in the middle. It should set the label attributes back to what they where originally. You'll need to re-arrange the gridding to have three columns, and move the exit button to the third column. Then create your new button and grid it into the middle. Give it a command function which uses configure to set everything back. Set its text and colors to appropriate values.

  6. Here's perhaps a better way to this. Please enter and save this program and run it. Again, save it on the desktop with a .pyw ending.

    # Simple button demo with dialogs.

    from Tkinter import *
    from tkMessageBox import *

    # Warn the user about having pushed the button.
    def warn():
        showwarning("You Don't Listen!", 'I TOLD you not to push that!')

    # Stop the program.
    def stop():
        response = askokcancel('Verify Exit', 'Do you really want to quit?')
        if response:
            root.quit()

    # Create the GUI.
    root = Tk()

    lab = Label(root, text="Don't Push The Button!",
                bg='blue', fg='white', relief='sunken')
    lab.grid(row=0, column=0, columnspan=2)
    but1 = Button(root, text="Push Me", command=warn,
                  bg='green', activebackground='#99FF99')
    but1.grid(row=1, column=0, sticky='news')
    but2 = Button(root, text="Exit", command=stop, bg='#FF9999')
    but2.grid(row=1, column=1, sticky='news')

    root.mainloop()
    Run the program a few times, then use the exit button.

    Notice that this one uses an additional library, tkMessageBox, from which come the showwarning and askokcancel dialog functions. Each of these takes two strings and creates a dialog window. (Notice where each string is displayed on the window.) These are modal dialogs, which means that you are forced to respond to the dialog window first, because the main window ignores all your clicking while the dialog is displayed.

    Our button functions, warn and stop, respectively call the showwarning and askokcancel. Showwarning just waits until you click OK. The confirmation dialog displayed by askokcancel returns a boolean, which is true if the user presses OK, and false if the presses Cancel.. The code then uses this response to decide to run quit or not.

    These are some of the simplest dialogs provided by tkMessageBox and some other dialog libraries. Some ask yes/no questions, or questions with more alternatives. Others ask for strings or numbers, select colors or choose files.

    C. Further Dialog

    1. Be an evil programmer and modify the above program so that it exits only when you don't cancel, and continues when you confirm the exit.
    2. Take the above example (with the original exit function) and modify the warn function so that after the warning dialog is closed it does the following:
      • Ask if the user would like to disable the button. Use the askyesno dialog, which is used just like askokcancel, but it shows Yes and No buttons. The function returns true when the user chooses Yes, and false for No.
      • If the user says Yes, do this:
        but1.configure(state="disabled")
        The button text will turn gray, and the button will stop responding.

  7. Here's a program for temperature conversions. Enter it in a new file window, and save it on the desktop with a .pyw ending.

    from Tkinter import *

    # These colors are set in several places, but this lets us change in
    # only one.
    mainbg = '#8888FF';
    activebg = '#AAAAFF';

    root = Tk()
    root.title('Temp Conversion')

    # This grids the object where indicated, then returns it.
    def mkgrid(r, c, w):
        w.grid(row=r, column=c, sticky='news')
        return w

    # This computes the Celsius temperature from the Fahrenheit.
    def findcel():
        famt = ftmp.get()
        if famt == '':
            cent.configure(text='')
        else:
            famt = float(famt)
            camt = (famt - 32) / 1.8
            cent.configure(text=str(camt))


    flab = mkgrid(0, 0, Label(root, text="Fahrenheit Temperature",
                              anchor='e', bg=mainbg))
    clab = mkgrid(1, 0, Label(root, text="Celsius Temperature",
                              anchor='e', bg=mainbg))

    ftmp = mkgrid(0, 1, Entry(root, bg=mainbg))
    cent = mkgrid(1, 1, Label(root, text="", relief='sunken',
                              anchor='w', bg=mainbg))

    elab = mkgrid(0, 2, Label(root, text='', bg=mainbg))
    fbut = mkgrid(1, 2, Button(root, text="Compute Celsius",
                               bg=mainbg, activebackground=activebg,
                               command=findcel))
    root.mainloop()
    Run the program. After the window appears, you can enter a temperature in the box by the label Fahrenheit Temperature, and press the Compute Celsius button. The equivalent Celsius temperature appears. Try converting several temperatures.

    Here are the new features demonstrated in this example:

    • The root.title() call takes a string which it displays on the frame of the main window. Notice that when the program runs, you see Temp Conversion on the window border instead of Tk.
    • The anchor attribute is used to move the text of a widget to a side of its area. It is similar to the sticky option to grid, except it does not allow stretching. It takes a list of compass points, but you may only specify a single side, such as w for the left, or a corner, such ne for the upper right. This example always grids with sticky='news' so the background color will stretch to fill the grid cell, then sometimes uses anchor to position the contents as desired.
    • The temperature to convert is received with an Entry widget. See below for more on how this is used.

    The function mkgrid is a minor convenience. It takes the row, column, and an object, grids the object there with sticky='news', then returns it. This saves typing a separate grid line and makes creating new widgets a little simpler.

    The box in which you enter the Fahrenheit temperature is the entry widget stored in the variable ftmp. An entry widget lets you enter and edit whatever you like, and remembers what you typed. Pressing the Compute Celsius button calls the findcel function, which uses the method ftmp.get() to ask the entry object what you have typed. It then tells the label cent to display the equivalent Celsius temperature. If you entered nothing, it sets a blank, otherwise it computes the correct value. The entry delivers a string, so we use the built-in float function to convert to floating point. Likewise, configure wants a string for the text attribute, and the built-in str function will provide that from the floating point number we compute.

    The color scheme for this window uses various blues. We start out the program by setting the variables mainbg and activebg to two light blue shades used for backgrounds. These are ordinary variables set to string values. They are colors only because they repeatedly appear with bg= and activebackground= when creating other widgets.

    The label cent is designed to look like an entry to give the overall window a symmetrical appearance. This is done by setting the relief and anchor to values which are defaults for an entry.

    In the upper right corner is a label with blank text. Its only purpose is to provide the background color in that grid slot.

    Examine the program so that you understand how it works. Experiment a bit to help you understand. Make each of these changes, run the program, then return to the original.

    • Remove the label with blank text to see how it changes the appearance of the window.
    • Change the sticky='news' in mkgrid to sticky='w' to see how that changes the appearance of the window.
    • Understand how mkgrid is used, and modify the program to create the cent label without using mkgrid. Remove the existing call, and replace it with the plain Label call followed by a grid call, such that the program behaves the same way.

    D. Raise the Temperature

    1. Change the color scheme by changing mainbg and activebg. Choose light colors so the lettering remains readable.
    2. The existing program only allows you to adjust the background colors easily. Add another variable for the text color and set it some reasonable value. Then, modify the program so all appropriate objects have the foreground color you set. That is, set the fg property to your new variable, just as the existing ones have bg and activebackground set. Try running with several different foreground colors to see if your change is effective. Then redesign the color scheme to your liking, making sure to retain good contrast.
    3. Add an exit button with a confirmation dialog. Pattern it after the one from the previous example. Place the button wherever you like; you may need to rearrange the existing parts.
    4. Add a clear button which blanks out both temperatures. Again, add it wherever seems best, and rearrange things if you like. To clear the entry widget, say ftmp.delete(0, END). The delete method allows you delete any of the characters in the entry; this form wipes 'em all. To clear the label, just configure with a text=''.
    5. Optional: Modify the program so that it will convert in either direction. Change the label cent which displays the compute Celsius temperature to be an entry. Add a Compute Fahrenheit button which will take the Celsius temperature and compute an equivalent Fahrenheit one. The reverse formula is f = 1.8c + 32. Each compute function will be setting its result into an entry instead of a label. To do this, first clear the entry as shown in the previous item, then set the new value with something of the form lab.insert(0, newvalue). You will also need to modify your clear button function.
The Tkinter has many more widgets, including large text areas, various menus, checkboxes, radio buttons, sliders and canvases for displaying arbitrary images. It is a very rich set of tools, of which we have provided only a taste. But that's all we set out to do.

Some Tkinter Links

A Tkinter Reference (New Mexico Tech)

This reference has a rather complete list of the available widgets, their attributes, and various methods to manipulate them.

Phone List Example

This is a short tutorial which takes you through the construction of a simple phone book program. It introduces a number of Tkinter features which we have not examined.

Tkinter Life Preserver
Fredrik Lundh's Tkinter Introduction

These are excellent tutorials on Tkinter. You will need to learn a bit about Python classes to get the most from them.


*In fact, there are some alternatives to grid layout.

This term is used by Tkinter and some other GUI systems which have roots in the Unix-based X-Window system created at MIT during the 1980s. Other GUI implementations use some far more boring term, such as Java's component.


Copyright 2005, 2006 Thomas W Bennet  •  Image Credits  •  Terms of use: Creative Commons