Graphical User Interfaces with Tkinter
Introduction
A Graphical User Interface (GUI) allows users to interact with a program through visual elements like buttons, text fields, and menus, rather than typing commands in a terminal. GUIs thus make applications more accessible to non-technical users.
How GUIs relate to OOP?
Each GUI element (widget), like a button or text field, can be thought of as an object of the corresponding class. These objects:
- have attributes (e.g., size, color, labels),
- have methods (e.g., click, enable/disable), and
- interact with each other through events,
following the principles of object-oriented programming (OOP).
Tkinter is the standard Python
library for creating graphical user interfaces. It provides a practical introduction to GUI development and event-driven programming. By using Tkinter, interactive applications such as calculators, form-based tools, or simple games, can be build. The Tkinter documentation can be found here:
Nevertheless, there are many other GUI libraries that you could be interested in. The Python
wiki lists several alternative GUI frameworks and tools.
Creating a Basic Tkinter Application
The Tkinter Main Loop
The root window is responsible for running the main event loop (mainloop()
), which keeps the GUI application running, listens for events (e.g., clicks, keystrokes), and updates the interface. The main event loop ends when the window is closed. Without the main loop, the GUI application would close immediately after being displayed.
import tkinter as tk
# Create root window
root = tk.Tk()
root.title("My First GUI Program")
root.minsize(width=300, height=400)
# Running the main loop event
root.mainloop()
Widgets and Controls
Widgets and controls are the building blocks of a Tkinter GUI. Widgets are structured in a hierarchy, where some widgets serve as containers (parent widgets - e.g., windows or frames) that hold other widgets (child widgets). This hierarchical arrangement defines the relationships and organization of widgets, influencing their layout, behavior, and interaction.
They are placed using a layout manager (place, pack, or grid).
Widgets feature attributes like text
, font
, bg
(background color), fg
(foreground color), and width
, which can be set during creation or updated later.
Collection of Different Widgets:
The following code provides a collection of different widgets: Label
, Input
, Button
, Text
, Spinbox
, Scale
, Checkbutton
, Radiobutton
, and Listbox
.
Each widget is an object of its class (e.g., button, label) and is part of the same parent widget (root
window).
Widget Callbacks:
Some widgets support event handling. Each widget's behavior is managed by a specific method:
- Button:
button_clicked()
- Spinbox:
spinbox_used()
- Scale:
scale_used(value)
- Checkbutton:
checkbutton_used()
- Radiobutton:
radio_used()
- Listbox:
listbox_used(event)
Dynamic Behavior:
Widgets like the spinbox, scale, and listbox dynamically print or process user interactions.
Event Binding:
The listbox
widget uses the bind
method to associate the selection event with the listbox_used
method.
Run the Code
Interact with the widgets to see their behavior in action (check the console for printed outputs).
import tkinter as tk
# Set window attributes
root = tk.Tk()
root.title("My First GUI Program and Widgets")
root.geometry("1024x768")
# Label
my_label = tk.Label(text="My First Label", font=("Arial", 24, "bold"))
my_label.pack()
# Input
my_input = tk.Entry(width=10)
my_input.pack()
# Button callback
def button_clicked():
print("Do Something Cool")
# Button
my_button = tk.Button(text="Click Me", command=button_clicked)
my_button.pack()
# Text
text = tk.Text(height=5, width=30)
text.focus()
text.insert(tk.END, "My first try on a multi-line text entry.")
print(text.get("1.0", tk.END)) # Prints the current text starting from line 1, character 0 ("1.0" == the very beginning of the text widget)
text.pack()
# Spinbox callback
def spinbox_used():
print(spinbox.get())
# Spinbox
spinbox = tk.Spinbox(from_=0, to=3, width=5, command=spinbox_used)
spinbox.pack()
# Scale callback
def scale_used(value):
print(value)
# Scale
scale = tk.Scale(from_=0, to=1000, command=scale_used)
scale.pack()
# Checkbutton callback
def checkbutton_used():
print(checked_state.get())
# Checkbutton
checked_state = tk.IntVar() # Variable to hold the checked state
checkbutton = tk.Checkbutton(text="Is This On?", variable=checked_state, command=checkbutton_used)
checkbutton.pack()
# Radiobutton callback
def radio_used():
print(radio_state.get())
# Radiobutton
radio_state = tk.IntVar() # Variable to hold the radio button selection
radiobutton1 = tk.Radiobutton(text="Answer1", value=1, variable=radio_state, command=radio_used)
radiobutton2 = tk.Radiobutton(text="Answer2", value=2, variable=radio_state, command=radio_used)
radiobutton1.pack()
radiobutton2.pack()
# Listbox callback
def listbox_used(event):
print(listbox.get(listbox.curselection()))
# Listbox
listbox = tk.Listbox(height=4)
for item in ["Red", "Green", "Blue", "Yellow"]:
listbox.insert(tk.END, item)
listbox.bind("<<ListboxSelect>>", listbox_used)
listbox.pack()
root.mainloop()
Tkinter Layout Manager
The layout of a graphical user interface is largely determined by the arrangement and design of the GUI components. Tkinter provides three different layout managers that automatically generate the layout based on the settings defined in the program. Without using a layout manager, the GUI element will not appear on the screen.
- Place Manager: Positions components based on specific coordinates (x, y), offering precise control over placement.
- Pack Manager: Automatically arranges components in a container, either vertically or horizontally, based on the order they are added.
- Grid Manager: Organizes components in a grid with rows and columns, allowing more complex layouts with alignment options.
Key Concepts of the Grid Layout Manager:
-
Rows and Columns:
- The grid is organized into rows and columns. You can place widgets in a specific row and column using the
grid(row, column)
method. - You can also span multiple rows or columns using
rowspan
andcolumnspan
attributes.
- The grid is organized into rows and columns. You can place widgets in a specific row and column using the
-
Control Placement:
- You can specify the row, column, and sticky options to control widget alignment (e.g., top, bottom, left, right).
-
Column and Row Configuration:
- Tkinter allows you to configure the weight of rows and columns to define how they should expand when the window is resized. You can do this using
grid_columnconfigure()
andgrid_rowconfigure()
.
- Tkinter allows you to configure the weight of rows and columns to define how they should expand when the window is resized. You can do this using
import tkinter as tk
root = tk.Tk()
root.title("Grid Layout Example")
# Label in the first row and first column
name_label = tk.Label(text="Name:")
name_label.grid(row=0, column=0)
# Entry in the first row and second column
name_entry = tk.Entry()
name_entry.grid(row=0, column=1)
# Label in the second row and first column
age_label = tk.Label(text="Age:")
age_label.grid(row=1, column=0)
# Entry in the second row and second column
age_entry = tk.Entry()
age_entry.grid(row=1, column=1)
# Button in the third row, spanning both columns
submit_button = tk.Button(text="Submit")
submit_button.grid(row=2, columnspan=2) # Spanning both columns
root.mainloop()
Tkinter Hints and Suggestions
-
Modularize Code
- Break the GUI into smaller classes or methods, particularly for large application.
- Use reusable components: e.g., set an
InputForm
class for repeated input forms.
-
Use Meaningful Variable and Method Names
- Name widgets and methods clearly to reflect their purpose. Avoid generic names like
button1
orlabel1
, use for examplesubmit_button
instead.
- Name widgets and methods clearly to reflect their purpose. Avoid generic names like
-
Avoid Hardcoding Layouts
- Use layout managers (
pack
orgrid
) instead of coding absolute positions (place(x, y)
). - Avoid mixing layout manager in the same container.
- Use layout managers (
-
Error Handling
- Add errror handling for user input or unexpected behavior.
- Provide clear instructions and feedback.
Example: Create Your Own To-Do List Application
The following code provides the basis for a simple To-Do List application with the following key features:
- Add Task: Enter tasks using the
Entry
widget and add them to theListbox
. - Cross Out Task: Mark the selected task with a "✔" symbol to indicate completion.
To-Do List Enhancements
Customize and enhance the To-Do List app with the following features:
1. Change the color scheme of the app.
2. Add a Button
to entirely delete tasks from the Listbox
.
3. Position the delete_button
next to the cross_out_button
and use a red color.
Bonus: Highlight important tasks by adding features like colored text or checkboxes to mark tasks as important.
import tkinter as tk
# Create the main window
root = tk.Tk()
root.title("To-Do List")
root.minsize(height=400, width=400)
root.configure(bg="#d0ebff") # Light blue background
# Function to add a new task
def add_task():
task = task_entry.get()
if task.strip():
task_listbox.insert(tk.END, task)
task_entry.delete(0, tk.END)
# Function to cross out a selected task
def cross_task():
selected = task_listbox.curselection()
if selected:
current_task = task_listbox.get(selected)
task_listbox.delete(selected)
task_listbox.insert(selected, f"✔ {current_task}")
# Entry box for adding tasks
task_entry = tk.Entry(width=30, font=("Helvetica", 14))
task_entry.grid(row=0, column=1, padx=10, pady=10)
# Add Task button
add_button = tk.Button(text="Add Task", font=("Helvetica", 12), bg="#b0e0e6", fg="black", command=add_task)
add_button.grid(row=0, column=0, padx=10, pady=10)
# Listbox to display tasks
task_listbox = tk.Listbox(width=40, height=15, font=("Helvetica", 12), selectbackground="#b3e5fc", selectforeground="black")
task_listbox.grid(row=1, column=0, columnspan=2, padx=10, pady=10)
# Cross Out Task button (centered)
cross_out_button = tk.Button(text="Cross Out Task", font=("Helvetica", 12), bg="#00264d", fg="white", command=cross_task)
cross_out_button.grid(row=2, column=0, columnspan=2, padx=10, pady=10)
# Start the main loop
root.mainloop()