© Springer-Verlag London 2014
Kent D. LeePython Programming FundamentalsUndergraduate Topics in Computer Sciencehttps://doi.org/10.1007/978-1-4471-6642-9_6

6. Event-Driven Programming

Kent D. Lee 
(1)
Luther College, Decorah, IA, USA
 
 
Kent D. Lee
When a program runs in Python the Python interpreter scans the program from top to bottom executing the first statement that is not part of a function definition. The program proceeds by executing the next statement and the next. Sequential execution is redirected by iteration (i.e. for and while loops) and function calls. Nevertheless, the program sequentially executes until Python interprets the last statement at which point the program terminates.
In an event-driven program sequential execution is in response to events happening while the program is executing. Event-driven programs arise in many areas of programming including Operating Systems, Internet Programming, Distributed Computing, and Graphical User Interfaces, often abbreviated GUI programs. An event-driven application begins as a sequential program executing one statement after another until it enters a never-ending loop. This loop, sometimes called the event dispatch loop looks for an incoming event and then dispatches that event to an event handler. Events come in a wide variety of flavors including:
  • An interrupt indicating the completion of a disk operation
  • A network packet has become available
  • A network connection has become unavailable
  • A button was pressed in a GUI application
  • A menu item was selected in a GUI application
  • An incoming request has been received by a web server.
In an event-driven program, the event dispatch loop looks for events like these. Each event will generally have its own event handler. An event handler is a function that is called to process the event. Each time an event is found, the corresponding event handler is called to process the event. Once the event is processed, the program returns from the event handler to the event dispatch loop to look for the next event. This process repeats forever or until some event is dispatched that causes the program to terminate. For example, if a user chooses to exit a GUI application, the event handler may tell the the event dispatch loop to quit and exit.
Tk is a powerful Application Programming Interface, or API, designed to make GUI programming easy on a variety of operating systems including Mac OS X, Windows, and Linux [11]. An API is a set of classes, or types, and functions that can be useful when implementing a program. In the case of Python, the Tkinter API was designed to allow Python programs to work with the Tk package to implement GUI programs that will run on Windows, Mac OS X, or Linux [5]. The Tkinter API is included in a module called tkinter. The module is included with most distributions of Python and may be imported to use in your Python programs.
Tk programs use widgets to build a GUI application. The term widget has been used at least since the 1980s to refer to any element of a GUI application including windows, buttons, menus, text entry fields, frames, listboxes, etc. There are many different widgets available in tkinter. Typically, any element you can see (and some you can’t see, like frames) in a GUI application is a widget. The next sections will introduce several widgets while building a Reminder! note application.

6.1 The Root Window

To begin using the Tk API you open a root window. Tk applications can have more than one open window, but the main window is called the root window. It is opened by calling a function called Tk().
Example 6.1
Here is code to open a Tk window.
A978-1-4471-6642-9_6_Figa_HTML.gif
A978-1-4471-6642-9_6_Fig1_HTML.gif
Fig. 6.1
A Tk root window
The code in Example 6.1 opens a window as pictured in Fig. 6.1. The call to the title method sets the title of the window. The call to resizable makes the window a non-resizable window. The Tkinter.mainloop() calls the Tk event dispatch loop to process events from the windowing application. Even with a simple window like this, the call to mainloop is required because there are events that even a simple window must respond to. For example, when a window is moved on the screen it must respond to its redraw event. Redrawing the window is done automatically by the Tk code once the mainloop function is called.
A978-1-4471-6642-9_6_Figb_HTML.gif

6.2 Menus

A menu can be added to the application by creating a Menu widget and adding it to the root window. On Windows and Linux the menu will appear right at the top of the window. On a Mac, the menu appears at the top of the screen on the menu bar. This menu contains a File-> Exit menu item that quits the application when selected.
Example 6.2
Here is the code that, when added right before the call to mainloop, creates a File menu with one menu item to exit.
A978-1-4471-6642-9_6_Figc_HTML.gif
When adding a menu, you associate a command (i.e. a function) with each menu item added to the menu. The Exit menu item is associated with the quit function which calls the root’s destroy method. Notice the quit function has no parameters. Most event handlers do not have parameters but do have access to the enclosing scope.
Practice 6.1
Write a Tkinter program that creates a main window with a menu that says Help. Within the Help menu item should be another menu item that says About. When the About menu is selected, your program should print “About was Selected” to the screen.

6.3 Frames

A Frame is an invisible widget that can be used as a container for other widgets. Frames are sometimes useful in laying out a GUI application. Layout refers to getting all the widgets in the right place and making them stay there even when the window is resized. We don’t have to worry about resizing the window in the Reminder! application so layout will be a little easier.
In Fig. 6.2 there is a Frame widget. The frame is invisible. The text entry area is inside the frame and so is the New Reminder! button. Frames can be useful to group widgets together. They can also have a border around them. The border around this frame is 5 pixels wide. Adding the frame with a border gives a little edge to the window.
Example 6.3
This is the code that creates the frame for the Reminder! application.
A978-1-4471-6642-9_6_Figd_HTML.gif
A978-1-4471-6642-9_6_Fig2_HTML.gif
Fig. 6.2
The main Reminder! window [9]
When the frame is created the first parameter to the Frame constructor is the window that the frame is to be packed into. This is true of every widget. The first parameter to the constructor when creating a widget is the widget it belongs to. In this way, widgets can be nested inside of widgets to form the GUI application. So, the mainFrame frame is a part of the root window. Recall that in Example 6.1 the variable root was set to the root Tk window.
Packing the mainFrame means to add it into the root window and make the contents of the frame visible. While a frame itself is invisible, by packing it the contents of the frame will be visible once the window is drawn. Packing is one method of making a widget visible. Other methods of making widgets visible are discussed in Sect. 6.9.
Practice 6.2
Create a frame and pack it in a root window.

6.4 The Text Widget

The Text widget is a powerful multi-line editing window that can embed graphics and other objects within it. In the Reminder! application it holds the message to be posted. The Text widget in this application is added to the mainFrame. By creating a Text widget and packing it into the main frame the user can enter text into it. The widget handles all the text entry itself without any intervention by the programmer.
Example 6.4
Here is the code to create a Text widget in the Reminder! application.
A978-1-4471-6642-9_6_Fige_HTML.gif
Practice 6.3
Create a text widget of 3 rows and 20 columns and place it in your practice GUI’s frame.

6.5 The Button Widget

The Button widget is used to get button press input from a user. Buttons appear in the native button format of the operating system you are using so they may not look exactly like the button displayed in Fig. 6.2. Since a button must respond to being pressed, when you create a button you specify an event handler to handle the button presses. An event handler is added to the button in the same way a command was added to a menu item in Sect. 6.2.
Example 6.5
Here is the code to create a Button and its associated event handler.
A978-1-4471-6642-9_6_Figf_HTML.gif
Example 6.5 shows a button being created, being added to the main frame, and then being packed within the frame. The keyword argument text specifies the text to go on the button. The keyword command is used to specify a parameterless function to call when the button is pressed. The function post is a parameterless function and is defined in the same scope as the Button. Normally, a function is not defined within the scope of another function. However, in Tk programming it is much more common. Event handlers are almost always nested functions. By nesting the event handler in the main function, it has access to all the variables defined in the main function. In this example the post function needs to have access to the root variable as well as the notes and reminders variables. By defining post within the same scope as the root variable, the post function can use these values as needed. Since the function post cannot have any parameters as dictated by Tkinter API, the post function must access the root variable from the enclosing scope. To see the whole program in context refer to Chap. 15.
A978-1-4471-6642-9_6_Fig3_HTML.gif
Fig. 6.3
A Reminder!
The post function gets the contents of the text field, called note, by using the get method on the note. Calling the get method with “1.0” and tkinter.END gets the text from beginning to end. The winfo_rootx() and winfo_rooty() methods get the x and y coordinates for the upper left corner of the root window. The post function then passes that information along with a couple of lists called notes and reminders to the addReminder function. The addReminder function adds a new reminder note to the screen as appears in Fig. 6.3.
Notice that when a command like post is provided to a button it is not written post(). This is because we are not calling post when the button is created. Instead, we are specifying that when the button is pressed the post function should be called. By providing the function name post to the button widget it can remember to call that function when it is pressed.
Practice 6.4
Create a button that says “Now!” on it. Connect it to a command that prints “Oh, now you’ve done it!” to the screen.

6.6 Creating a Reminder!

To create a Reminder! window another top level window is created. To do this, the button calls the addReminder function. There are two parts to a reminder, the window itself and the Text widget within the window. A list of reminder windows is maintained in a list called notes. A list of the text widgets is maintained in a list called reminders. These lists are parallel lists. This means that the first entry in both lists corresponds to the first reminder, the second element in both lists is the second reminder and so on. Parallel lists were first introduced in Sect. 4.​9 on p. 103. Both the window and the Text widget are needed to maintain the information about a reminder in the program.
Example 6.6
Here is the code that adds reminders to the screen. The notes and reminders lists keep track of the windows and Text widgets.
A978-1-4471-6642-9_6_Figg_HTML.gif
To add a reminder to the screen a toplevel window is created, the new window is not resizable and is positioned over the top of the existing window using the geometry method. Calling geometry on a window with a string like “+10+10” positions the window at (10,10) pixels measured from the upper left corner of the screen. Since the root window’s coordinates were passed to the function, the new window is positioned approximately on top of the root window.
The text is copied into the reminder. Then the window and the Text widget are copied into the notes and reminders lists, respectively. The last line of the method adds an event handler for the window deletion event. If the reminder window is closed, the user is getting rid of that reminder. In that case, the reminder window and corresponding Text widget are removed from the notes and reminders lists. The remove method looks for a matching element of the list and removes it. The only matching element of a window or Text entry widget is the original window or widget added to the list.
The deleteWindowHandler function is a case where accessing the enclosing scope is exactly what we want. We can’t pass parameters to the deleteWindowHandler function, but we can access the notes, reminders, reminder, and notewin variables from the enclosing scope to remove the window from the program when it is closed.

6.7 Finishing up the Reminder! Application

There is only a little more code needed to finish the Reminder! application. It is more interesting if the reminders are saved to a file when the program is closed. Then the reminder windows can be redisplayed when the program is started again. The application saves the information in a file called reminders.txt. The file starts with the X,Y coordinate of the root window on the screen. Then, each reminder record starts with an X,Y coordinate of the reminder window followed by some text on multiple lines followed by a line of underscores and periods in a pattern that should never be seen by accident. The application reads from the file until this special line is found and then makes a reminder out of the text it just read. Then it continues reading the file looking for the next reminder.
Example 6.7
Here is the code that reads and writes the reminders.txt file.
A978-1-4471-6642-9_6_Figh_HTML.gif
The code in the try...except block attempts to read the information when the application starts. This code is located in the main function of the application. When the window deletion event occurs for the main window, the appClosing handler is called. The appClosing function writes the file, overwriting any file that was read when the application started. The complete code for the Reminder! application can be found in Chap. 15.

6.8 Label and Entry Widgets

Assume we wish to enhance the Reminder! application by allowing the user to set the title of each reminder. Instead of the reminder note just having Reminder! as its title, it could have a user-defined title. So when the New Reminder! button was pressed for the application in Fig. 6.4 a new window would appear with “Don’t forget trash!” as its title. This can be done by adding a label and an entry widget to the application.
A978-1-4471-6642-9_6_Fig4_HTML.gif
Fig. 6.4
A titled Reminder! application
The Label widget is the text “Title:” that appears in the figure. The Entry widget is the one line text field. While a Text widget can handle multiple lines, an Entry widget holds just one line of text.
Example 6.8
Here is the code for the Entry and Text widgets in this application.
A978-1-4471-6642-9_6_Figi_HTML.gif
A new frame is created because it will need to contain the two elements on one line in the application. Without a new frame, the “Title:” label would be packed above the Entry widget. Within the titleFrame frame, the titleLabel and titleText widgets are added using the grid layout instead of the pack layout. In a grid layout you specify which row and column of the grid the widget should be placed in. The columnspan argument specifies that the titleText widget should span 2 of the three columns of the row.
A StringVar is an object with a get and a set method. The titleText Entry widget is created specifying a textvariable called noteTitle which is required to be of type StringVar. To retrieve the text of the Entry widget we can write noteTitle.get() and to set the text of the widget we can write noteTitle.set(“Whatever Text We Want”). StringVars make it easy to set and retrieve text from an Entry widget.
There is a little more code to write to complete the extension of this application to include the title information in the reminders and in the text file that stores the reminders. This code is left as an exercise.
Practice 6.5
Add a label that says “What do you want?” to the practice Tk application from this chapter.

6.9 Layout Management

When widgets are packed or gridded in an application, their appearance within the application is called their layout. Sometimes, when widgets are placed within an application they appear in the right place when the application starts, but if the window is resized, they don’t look right. Understanding something about layout management can help you correctly plan your application’s layout and avoid these kinds of problems.
Packing widgets places them one above another in what is sometimes called a flow layout. Each widget appears above the next when packed. The Tk packer is responsible for packer layout management. There are some options that can affect how packing is done. Normally the packer places one widget above another in a flow layout. But these options let the programmer have some control about how that flow is managed.
  • fill = You can specify that if a widget can use the extra space, then it should fill the available space. Valid values for fill are tkinter.X, tkinter.Y, or tkinter.BOTH. X means to fill in the horizontal direction, Y means to fill in the vertical direction, BOTH means to fill in both directions. For a label to fill in the horizontal direction you would write:
    A978-1-4471-6642-9_6_Figj_HTML.gif
    The bg and fg parameters set the background and foreground color, respectively.
  • side = This specifies which side to flow from. For example, writing titleLabel.pack(side=tkinter.LEFT) will flow from the left rather than the top. Other valid values are TOP, BOTTOM, or RIGHT.
The Tk gridder is responsible for grid layout management. Grid layout allows widgets to be placed in a specific column and/or row of a container widget. As we have seen, it is possible for one widget to span more than one column or row in a grid. The rowspan parameter sets the number of rows a widget should span. The columnspan option was used in Example 6.8. It is also possible to tell the gridder how it should use the space within a row and column. Normally a widget is centered within the available space. But, if the widget can use it, the gridder can be told to expand the widget to take up the available space. The sticky option tells the gridder to stick the widget to one or more sides of the available area. The tkinter.E and tkinter.W constants stand for east and west. By adding east and west together in Example 6.8 the entry widget will expand to the full width of its allowable size. In that example it has no affect on the layout, since the window cannot be resized anyway, but nonetheless it demonstrates its use.
While packing and gridding are the two most common forms of layout management, there is also a placer. The placer places widgets explicitly within the X,Y plane of the application. The packer, gridder, and placer are the three layout managers for Tkinter. Each of these layout managers have more options available for layout that are not discussed here but can be found by searching for “tkinter layout management” on the internet.
Practice 6.6
Make the entry widget and the button widget in your practice application appear next to each other at the bottom of the window.

6.10 Message Boxes

Sometimes it is necessary to pop up a message box in a GUI application to warn the user of some invalid operation they are trying to perform. Sometimes the application just needs to provide some quick feedback, like “Job Completed” or some other status. Tk provides a few message boxes for these occasions. To use the message boxes you must import tkinter.messagebox.
Here are three examples.
  • tkinter.messagebox.showinfo(“Invalid Entry”, “Type a reminder first.”)
    This displays an informational box with an informational icon. You can change the icon displayed in the box by specifying the icon = parameter. More information is available online. The dialog box appears on the screen and the application waits for OK to be pressed.
  • tkinter.messagebox.showwarning(“Invalid Entry”, “Type a reminder first.”)
    This works the same as the showinfo dialog box but displays a warning icon instead of an informational icon.
  • answer = tkinter.messagebox.askyesno(“Really?”, “Are you sure you want to create a blank reminder?”)
    This displays a dialog with Yes and No buttons. If Yes is pressed, the function call returns True. If No is pressed, the function returns False.
A978-1-4471-6642-9_6_Figk_HTML.gif
There are other dialogs available including a color chooser and file chooser. There are also several other options that are possible with each of these dialogs. Again, more information can be found online.
Practice 6.7
When the button of your practice application is pressed, take the information in the entry widget and display it in a message box of your choice with some appropriate text to go with it.

6.11 Review Questions

  1. 1.
    How are a event-driven program and simple sequential program the same?
     
  2. 2.
    What distinguishes an event-driven program from a sequential program?
     
  3. 3.
    What is an API?
     
  4. 4.
    Name two APIs that are available in Python. What does each API do for you as a programmer?
     
  5. 5.
    What is a widget?
     
  6. 6.
    When writing a Tkinter application, what is the purpose of the call to mainloop?
     
  7. 7.
    What is the purpose of a frame in Tkinter?
     
  8. 8.
    What does the term layout refer to in a GUI application? Be complete in your answer.
     
  9. 9.
    What is the purpose of the StringVar class in Tkinter applications?
     
  10. 10.
    Why are event handlers generally defined within the scope of the main function?
     
  11. 11.
    What are two methods of arranging widgets in a Tkinter application? Describe the differences between the two methods.
     

6.12 Exercises

  1. 1.
    Extend the Reminder! application so that each Reminder! is given the title assigned in the main application window. For example, if the New Reminder! button is pressed for the application as it appears in Fig. 6.4, the reminder window would appear as shown in Fig. 6.5. Be sure to clear both the text and the title from the root application window after the New Reminder! button is pressed.
     
  2. 2.
    Implement a GUI front-end to the address book application. The GUI should be similar to that presented in Fig. 6.6. Each of the buttons in the application should work as described here.
    1. (a)
      The add button should add a new entry to the phonebook. This must append an entry to the phonebook. The event handler for this function should look something like this (depending on how you write the rest of your program).
      A978-1-4471-6642-9_6_Fig5_HTML.gif
      Fig. 6.5
      A titled Reminder!
      A978-1-4471-6642-9_6_Fig6_HTML.gif
      Fig. 6.6
      A GUI for the addressbook application
      A978-1-4471-6642-9_6_Figl_HTML.gif
       
    2. (b)
      The update button should update an existing entry or display a message saying the entry was not found. Update must find an entry that matches the first and last name displayed in the GUI. If found, the entry in the file is updated to reflect the new information found in the GUI. You find an entry by matching the first and last name in the address book so updating the name will not work. In that case a new entry needs to be added and the old one deleted. If the entry is not found a warning message should be displayed.
      Since entries cannot be deleted from files, to update an entry you must open a new file for writing. Then you copy all the entries to the new file that don’t match the entry to be updated. Once you find the entry to be updated you write the GUI information to the new file. Finally, you must write the rest of the non-matching entries to the new file. After you are done, you can remove the old file and rename the new file to the addressbook.txt file name. The following lines of code will delete the addressbook.txt file and rename a file called __newbook.txt to addressbook.txt.
      A978-1-4471-6642-9_6_Figm_HTML.gif
       
    3. (c)
      The delete button deletes an existing entry. To delete an existing entry the last and first name should match the entry being deleted. Since you cannot delete a record from a file, you must create a new file, writing all records to the new file except for the one to be deleted. Then remove the old file and rename the new file to addressbook.txt. See the description of the update button implementation to see how to delete and rename the files.
       
    4. (d)
      The find button finds the entry with the same first and last name as typed. It should at least work when both last and first name are supplied by the user. However, you can extend this by making it work if the last name is empty. Then it should match only on first name. Likewise, if the first name is empty then it should only match on last name. In either case it should display the first matching entry in the address book.
       
    5. (e)
      The next button displays the next address after the current entry and wraps around to the beginning when the last entry was displayed.
       
     
  3. 3.
    Implement a GUI front-end for the addressbook application as described in Exercise 2, but use parallel lists to hold the fields of each record instead of reading from and writing to the file immediately. You should write code to read the entire file when the application starts and it should be written again when the application closes.
    Each of the buttons should be implemented but instead of reading or writing to the file, the buttons should use the parallel lists as the source of the addressbook entries.
     
  4. 4.
    Using the Reminder! application code from Appendix 15 as a reference, rewrite the code so that the reminders are read from an XML file when the application starts and are written to an XML file when the application terminates. To write an XML file you open a text file for writing and you write the data and the XML tags for each XML element.
     
  5. 5.
    Implement a GUI front-end for the addressbook application but in this version of the application define an XML file format to hold the data. Then, write the program to read the XML file when the application starts and write the XML file when the application terminates. Use parallel lists to hold the fields of each record while the application is running. To write an XML file you open a text file for writing and you write the data and the XML tags for each XML element.
     

6.13 Solutions to Practice Problems

These are solutions to the practice problems in this chapter. You should only consult these answers after you have tried each of them for yourself first. Practice problems are meant to help reinforce the material you have just read so make use of them.

6.13.1 Solutions to Practice Problem 6.1

A978-1-4471-6642-9_6_Fign_HTML.gif

6.13.2 Solutions to Practice Problem 6.2

The window will probably resize to a very tiny window when run because there isn’t anything in the frame yet.
A978-1-4471-6642-9_6_Figo_HTML.gif

6.13.3 Solutions to Practice Problem 6.3

A978-1-4471-6642-9_6_Figp_HTML.gif

6.13.4 Solutions to Practice Problem 6.4

A978-1-4471-6642-9_6_Figq_HTML.gif

6.13.5 Solutions to Practice Problem 6.5

A978-1-4471-6642-9_6_Figr_HTML.gif

6.13.6 Solutions to Practice Problem 6.6

A978-1-4471-6642-9_6_Figs_HTML.gif

6.13.7 Solutions to Practice Problem 6.7

A978-1-4471-6642-9_6_Figt_HTML.gif