A  GUIde for Writing Programs with Graphical User Interfaces


There are lots of ways you can use Tkinter, the built-in graphical user interface library in Python, to create  applications.  I'm sure that a true Python expert would give different advice from what you read here. This guide is designed to be relatively simple, and, insofar as Tkinter permits this,  to promote good programming practices. My guiding principle has been to keep the logic of the application completely independent of the graphical user interface.

Even though this is a very brief document about Tkinter, it contains more information than I want you to keep in you head!  This really is a cookbook:  copying the recipe and changing a few ingredients will take you a long way. 

The template has four parts:

imports

application-specific functions

event-handler

main loop

Not every part will appear in every GUI program.   Applications that only use drawing and do not  involve buttons, like the happyface.py program with which the course began, do not require an event handler.

Imports

Begin the program with

from tkinter import *

and then import whatever other libraries are needed.  In our examples, we've imported  random for the random-number generator.

Application-specific Functions

The basic logic of the application should be independent of the graphical user interface.  By that I mean that you should literally be able to take the identical functions, drop them into another application, and have them work. 

This is illustrated in the GUI triangle example posted on the course website--I just used the same area and distance functions that we had developed for the original non-GUI version.

In some of our examples, the application-specific functions actually drew something, so we could not avoid Tkinter-specific graphical elements.  But we still structure these as

def draw_checkerboard(can,  )

We make the drawing canvas a parameter of the function instead of using the name of the canvas defined in the main event loop.  By doing so, we can copy this function and paste it in a different Tkinter application in which the canvas is called something else, or in which there is more than one canvas. 

Main event loop

We'll discuss this before the event handler, although in the program itself, I suggest you put it after the event handler, at the very end.

The main loop always begins with a line like

win = Tk()

and ends with

win.mainloop()

The name win is just an ordinary variable name; you can call it what you want, it just has to be the same name in both these statements.

In between, you create widgets and place them in the window.  Widgets include canvases, buttons, labels, single-line and multi-line text-entry fields, and a lot of other things that we will not see in this course.  In all cases, creation and placement of the widgets is a two-step process.

Creation of the widget always looks something like this:

this_new_button = Button(win,....)

The first argument to Button is the parent widget.  Very often this is the original window, but we can also place widgets inside of other widgets.  The other arguments specify attributes of the widget (the size of a canvas, the text to appear in the button, etc.)

There are several options for placing widgets in the window. I suggest using the grid method of the widget:

this_new_button.grid(row =3,column=0)

which specifies the position of the widget in its parent (in the fourth row and the first column---row and column numbers are zero-based).

For Canvas, Entry and Label widgets, at least in the first few applications we will make, you only have to do the creation and placement.  For Button widgets, you want them to do something, and for this you need to link the button to a function that will say what to do when the button is clicked.  So you will need to add a line:

this_new_button.bind('<Button-1>',button_handler)

Here button_handler is the name of a function that you will supply (and you can call it something else if you want).  This tells Tkinter what function to call whenever there is a click of the left mouse button.  In our applications, we will only use these 'event handlers' for button widgets.  By the way, 'Button-1' refers to the left button on the mouse, not the button widgets you are creating. The next section explains the event handler.

Where can you go to read about the many different kinds of widgets and the many, many different methods they support?  I have found the documentation at this link and this one to be most useful. But be careful!  These documents were written a long time ago, with Python 2 in mind, and some things have changed.  The biggest thing is that first line, from tkinter import *, which in Python 2 had upper-case 'T' instead of lower-case 't'. But as a reference for what each of the individual widgets dows, I found these still accurate, and indispensable.

The event handler

The event handler is the glue that attaches the graphical user interface to the application logic.  In our template, it responds to clicks in button widgets and calls the application-specific functions.  The event handler has access to the global names of the widgets created in the main application loop, one of the few instances in which we will use variables with global scope.  For instance, it may get the value of an argument from an entry field, pass it to one of the application-specific functions, and use the value returned to update the entry field.

The general form is

def button_handler(event):
   
wid=event.widget
    if wid==this_button:
        #do stuff with the widgets and the application-specific functions
    elif wid==that_button:
        #do some other stuff   
    elif wid== another_button:
        #or something else entirely

Entry widgets

The Entry widget is a text field.  You create them and place them just like the other widgets.  To retrieve the information from the Entry, you can write

s=this_entry.get()

which returns a string.  To write a string s in the Entry, you can use


this_entry.delete(0,END)
this_entry.insert(0,s)

That is, you have to first delete the present contents of the Entry, starting at position 0, and then insert the new contents before position 0.  This is a bit annoying, but the documentation does not describe any methods for replacing the old contents of an Entry in a single statement.

update_idletasks()

You may find that drawing in a canvas is not carried out when the drawing statement ought to be executed.  Writing

win.update_idletasks()

where win is the name assigned to the window, before the call to the drawing function, fixes this.  (The reason is something like this: The statements in the GUI program are not executed in lockstep, where one statement is completely executed before the next one is processed, but instead in a more asynchronous fashion where some tasks are postponed until others, invoked later, have completed.  So this statement causes postponed drawing statements to complete execution. But honestly, it's a bit of a mystery to me.)

Reduced Versions

If the GUI does not need to respond to events like mouse clicks, we can do away with the event handler.  This was the case with the drawing programs we saw earlier.  If the actions in response to buttons are very simple, we can embed the code for them directly in the event handler---we could do this for instance in the example below.  But I suggest you NOT do this:  few of our examples will be so simple that this is a workable alternative, and it is always a good idea to think of the application logic as separate from the GUI.

Example

Several examples are posted on the website.  Below is a detailed description of a particularly simple one.  An application with much the same structure is the calculator for finding the area of a triangle.

We will construct an application with two buttons, labeled 'Jack' and 'Jill' that display messages when clicked.  To make this boring program just a little less boring, we will have the program choose the messages at random from a small selection of possibilities.

Here are the application-specific functions.  Note once again that they make no reference to GUI elements.The function randint returns a random integer between the two bounds given.

import random

def jack():
    choice = random.randint(0,1)
    if choice==0:
        return "Help! I'm falling down!"
    else:
        return "Oh no!  I broke my crown!"

def jill():
    choice = random.randint(0,1)
    if choice==0:
        return "Jack, wait for me!"
    else:
        return "OMG, I'm tumbling!"

We can test these just as they are, before we supply the GUI.

Now let's design the GUI.  Our application will have a single-line Entry widget to display the text, and two buttons.  We'll put the entry widget above the buttons.  The width property of Entry specifies how many characters wide to make this field.  The columnspan property of the grid method tells Tkinter that this widget should span two columns.  I've commented out the line binding button-click events to the event handler function, since we haven't written the handler yet.

from Tkinter import *

win = Tk()
message=Entry(win,width=40)
message.grid(row=0,column=0,columnspan=2)
jackbutton=Button(win,text='Jack')
jackbutton.grid(row=1,column=0)
jillbutton=Button(win,text='Jill')
jillbutton.grid(row=1,column=1)
#win.bind('<Button-1>',button_handler)
win.mainloop()


You can also test this separately, to make sure it looks right.

Finally, we'll put the whole thing together by adding the glue of the event handler.  The complete program is shown below.  Note that we rely on particular methods of the Entry widget to insert and delete text from the entry field.

#Jack and Jill

from Tkinter import *

import random

def jack():
    choice = random.randint(0,1)
    if choice==0:
        return "Help! I'm falling down!"
    else:
        return "Oh no!  I broke my crown!"

def jill():
    choice = random.randint(0,1)
    if choice==0:
        return "Jack, wait for me!"
    else:
        return "OMG, I'm tumbling!"

def button_handler(event):
    win.update_idletasks
    widg=event.widget
    if widg==jillbutton:
        message.delete(0,END)
        message.insert(0,jill())
    elif widg == jackbutton:
        message.delete(0,END)
        message.insert(0,jack())

win = Tk()
message=Entry(win,width=40)
message.grid(row=0,column=0,columnspan=2)
jackbutton=Button(win,text='Jack')
jackbutton.grid(row=1,column=0)
jillbutton=Button(win,text='Jill')
jillbutton.grid(row=1,column=1)
win.bind('<Button-1>',button_handler)
win.mainloop()