15.26. The Design of GUI Programs¶
For very simple GUI programs, no special program design is needed, as
demonstrated in the previous “Hello World” example programs. However, any
non-trivial GUI program will require extensive use of global variables if
the structure of the code does not use a Python class
. You have learned
in previous lessons that global variables are bad because they make
debugging programs more difficult. Therefore we want a design for GUI
programs that avoids global variables as much as possible.
To demonstrate this, let’s look at two versions of a simple program that
increments a counter each time a user clicks a button. The first version
of this code does not use a class
definition and requires that a
global variable called my_counter be used. This is because the label that
represents the counter is created in the create_user_interface
function
but it must be accessed in the event handler function increment_counter
.
In fact, the event handlers of a GUI program almost always need access to multiple
widgets in the program’s interface and the values can’t be passed as
parameters because an command
event handler
function receives no
parameters and a bind
event handler
function receives exactly one
parameter – an event object
. Study the following example and
pay close attention to where the my_counter
global variable is used.
import tkinter as tk
from tkinter import ttk
global my_counter
def create_user_interface(application_window):
global my_counter
my_counter = ttk.Label(application_window, text="0")
my_counter.grid(row=0, column=0)
increment_button = ttk.Button(application_window, text="Add 1 to counter")
increment_button.grid(row=1, column=0)
increment_button['command'] = increment_counter
quit_button = ttk.Button(application_window, text="Quit")
quit_button.grid(row=2, column=0)
quit_button['command'] = window.destroy
def increment_counter():
global my_counter
my_counter['text'] = str(int(my_counter['text']) + 1)
# Create the application window
window = tk.Tk()
create_user_interface(window)
# Start the GUI event loop
window.mainloop()
Let’s compare the above program to an identical application that is designed
as a Python class
. The class
encapsulates all of the values needed
for the GUI interface and the event handlers
and we don’t need global
variables!
import tkinter as tk
from tkinter import ttk
def main():
# Create the entire GUI program
program = CounterProgram()
# Start the GUI event loop
program.window.mainloop()
class CounterProgram:
def __init__(self):
self.window = tk.Tk()
self.my_counter = None # All attributes should be initialize in init
self.create_widgets()
def create_widgets(self):
self.my_counter = ttk.Label(self.window, text="0")
self.my_counter.grid(row=0, column=0)
increment_button = ttk.Button(self.window, text="Add 1 to counter")
increment_button.grid(row=1, column=0)
increment_button['command'] = self.increment_counter
quit_button = ttk.Button(self.window, text="Quit")
quit_button.grid(row=2, column=0)
quit_button['command'] = self.window.destroy
def increment_counter(self):
self.my_counter['text'] = str(int(self.my_counter['text']) + 1)
if __name__ == "__main__":
main()
Notice the following about this design:
The application’s window is created in the constructor (
__init__
) of theCounterProgram
class and then the interface widgets are created by a call tocreate_widgets
.The event handler,
increment_counter
can access the labelself.my_counter
using the object’s attributes.The code creates an instance of the class
CounterProgram
and starts the GUI event-loop.
It is recommended that you develop all of your GUI programs as Python Classes.
For complex designs, a Python Class
can help manage the complexity of
the code and the scoping of variables.