Skip to main content

Graphical User Interfaces with Tkinter

A Graphical User Interface (GUI) allows users to interact with software through visual indicators and graphical icons, rather than purely text-based command-line interfaces. While building a GUI introduces additional complexity—requiring layout management for windows, buttons, menus, and text fields—it provides an intuitive way for users to interact with applications, dramatically improving the user experience.

For Python developers, the standard tool for building GUIs is Tkinter. As Python's built-in GUI library, it is included in the standard library, allowing you to create cross-platform graphical applications immediately without installing third-party packages.

Tkinter acts as a Python wrapper around Tcl/Tk, where Tcl is a scripting language and Tk is a cross-platform GUI toolkit. Tkinter automatically coordinates with the host operating system to render widgets with a native look and feel. Tkinter's interface elements—such as windows, buttons, labels, and text boxes—are known as widgets. Each widget can be customized in terms of appearance and behavior, and they can be nested and arranged to build complex application layouts.

Because GUI applications render local windows, they cannot be run in web-based sandboxes or online notebook environments; you must run the following examples on your local computer.

Creating a Window

Here is a basic "Hello, World!" graphical application that opens a window containing a text label:

import tkinter as tk

# Create a new window
root = tk.Tk()

# Set window title
root.title("Hello World Program")

# Create a label widget with text "Hello, World!"
label = tk.Label(root, text="Hello, World!")

# Place the label widget on the window
label.pack()

# Run the event loop, waiting for user interaction
root.mainloop()

The code begins by importing tkinter as tk. It instantiates the main window container using tk.Tk(), assigns it a title, and creates a text-displaying Label widget.

Calling the label's pack() method instructs the geometry manager to place the widget inside the main window, auto-adjusting its dimensions. Finally, root.mainloop() kicks off the event loop, running continuously to listen for user actions (like resizing or closing the window). This is the standard execution template for any Tkinter application.

The program runs as follows:

Widget Layout

Tkinter provides three geometry managers to handle widget layouts: pack(), grid(), and place().

  • pack(): Stacks widgets sequentially against the edges of their parent container (top, bottom, left, or right).
  • grid(): Positions widgets in a two-dimensional grid of rows and columns, similar to a spreadsheet.
  • place(): Places widgets at absolute coordinates or positions relative to the window size.

You can combine different layout managers within the same application, but you must never mix them within the same parent container (e.g., calling both pack() and grid() on different child widgets inside the same Frame or window will cause the GUI engine to freeze or crash). You can, however, nest a Frame that uses pack() inside a window that uses grid(), which is a common design pattern.

Here is an example program that uses different layout managers to place multiple widgets in different positions on the window:

import tkinter as tk

# Create main window
root = tk.Tk()
root.title('Tkinter Layout Managers')

# Calculate window position to center it
window_width = 600
window_height = 400
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
center_x = int(screen_width/2 - window_width / 2)
center_y = int(screen_height/2 - window_height / 2)

# Set window size and position
root.geometry(f'{window_width}x{window_height}+{center_x}+{center_y}')

# 1. Use pack() layout
label1 = tk.Label(root, text='Packed Label (Top)', bg='lightblue')
label1.pack(side='top', fill='x')

# Create a Frame container, inside which we will use grid()
frame = tk.Frame(root, borderwidth=2, relief='sunken')
frame.pack(side='top', padx=5, pady=5)

# 2. Use grid() layout inside the Frame
label2 = tk.Label(frame, text='Grid Label (Row 0, Col 0)')
label2.grid(row=0, column=0, padx=5, pady=5)

label3 = tk.Label(frame, text='Grid Label (Row 0, Col 1)')
label3.grid(row=0, column=1, padx=5, pady=5)

label4 = tk.Label(frame, text='Grid Label (Row 1, Col 0-1)')
label4.grid(row=1, column=0, columnspan=2, padx=5, pady=5)

# 3. Use place() for absolute positioning
# Create an entry widget
entry = tk.Entry(root)
entry.place(x=50, y=200)

# Create a button
button1 = tk.Button(root, text='Placed Button (Abs)')
button1.place(x=200, y=200)

# Use place() for relative positioning (relative to parent container)
button2 = tk.Button(root, text='Relatively Placed Button (Center)')
button2.place(relx=0.5, rely=0.8, anchor='center')

# Run the event loop, waiting for user interaction
root.mainloop()

This code configures a window and centers it on the screen, then instantiates multiple widgets (including an input Entry box, buttons, and a sub-container Frame) using a combination of the three geometry managers.

User Events

A GUI responds to user actions (like button clicks, keypresses, or mouse movements) using event-driven programming. We bind a callback function to a specific event; when that event occurs, the GUI engine automatically executes the function.

import tkinter as tk

def handle_user_input():
# Get the content entered by the user in the input field
user_input = entry.get()

# Use the obtained content for processing; here, display it in a label
result_label.config(text=f"You entered: {user_input}")

# Create main window
root = tk.Tk()
root.title('User Input Processing Example')
root.geometry('300x200')

# Create an entry widget
entry = tk.Entry(root)
entry.pack(pady=10)

# Create a button that processes user input on click
# The command parameter binds the callback function
submit_button = tk.Button(root, text="Submit", command=handle_user_input)
submit_button.pack(pady=10)

# Create a label to display the result
result_label = tk.Label(root, text="Please enter content")
result_label.pack(pady=10)

# Start the Tkinter event loop
root.mainloop()

In this example, the Button's command parameter is set to the handle_user_input function. Clicking the button triggers the callback, where entry.get() retrieves the text inside the input box and result_label.config() updates the label dynamically.

We can also bind callback functions directly to specific mouse or keyboard events. For example, the following program detects when the mouse cursor enters the boundaries of a button. The moment it does, the button is randomly relocated, making it impossible to click:

import tkinter as tk
import random

# Create main window
root = tk.Tk()
root.title("Teasing Button")
root.geometry('600x400') # Larger window so the button has more room to move

# Function to move the button
def move_button(event):
# The event parameter contains detailed info about the triggering event, such as mouse position
button_width = button.winfo_width()
button_height = button.winfo_height()

# Ensure the button doesn't move outside the window boundary
max_x = root.winfo_width() - button_width
max_y = root.winfo_height() - button_height

new_x = random.randint(0, max(0, max_x))
new_y = random.randint(0, max(0, max_y))

button.place(x=new_x, y=new_y)

# Create a button to move
button = tk.Button(root, text="Catch me!")
button.place(x=50, y=50) # Initial position

# Bind the mouse entering button event (<Enter>) to the move_button function
button.bind('<Enter>', move_button)

# Start the event loop
root.mainloop()

Simple Animation

By repeatedly updating a widget's position on a timer, you can create animations. Tkinter provides the .after(delay_ms, callback) method to schedule a function to run after a specific delay, allowing for automated, event-driven loops without blocking the main event loop:

import tkinter as tk

# Create main window
root = tk.Tk()
root.geometry('600x400')

# Create a button to move
button = tk.Button(root, text="Auto Run")
button.place(x=50, y=50) # Initial position

# Function to auto-move the button
def auto_move_button():
# Get current button position
current_x = button.winfo_x()
current_y = button.winfo_y()

# Update button position
new_x = current_x + 5
if new_x < root.winfo_width() - button.winfo_width():
button.place(x=new_x, y=current_y)
else:
button.place(x=0, y=current_y) # Reset if it reaches the boundary

# Call this function again every 50ms to create a loop
root.after(50, auto_move_button)


# Call the function once before the event loop starts to kick off the loop
root.after(50, auto_move_button)

# Start the event loop
root.mainloop()

For drawing complex shapes or animations, instead of moving widgets, it is much more efficient to use a Canvas widget. The Canvas provides methods for drawing shapes, lines, and images. Here is a simple demonstration of an automated loop animating a bouncing ball inside a Canvas:

import tkinter as tk

# Initialize main window
root = tk.Tk()
root.title('Bouncing Ball')
root.geometry('600x400')

# Create a canvas
canvas = tk.Canvas(root, bg='white')
canvas.pack(fill=tk.BOTH, expand=True)

# Create a ball (x1, y1, x2, y2)
ball = canvas.create_oval(10, 10, 60, 60, fill='blue', outline='white')

# Set ball movement speed
speed_x = 3
speed_y = 3

# Update ball position
def move_ball():
global speed_x, speed_y

# Get the ball's current position (x1, y1, x2, y2)
coords = canvas.coords(ball)

# Ensure the window hasn't been closed
if not coords:
return

ball_left, ball_top, ball_right, ball_bottom = coords

# Check if hitting window edge
# winfo_width() gets the actual width, might need update() to be accurate; simplified here
canvas_width = canvas.winfo_width()
canvas_height = canvas.winfo_height()

if ball_left <= 0 or ball_right >= canvas_width:
speed_x = -speed_x # Bounce horizontally
if ball_top <= 0 or ball_bottom >= canvas_height:
speed_y = -speed_y # Bounce vertically

# Move the ball
canvas.move(ball, speed_x, speed_y)

# Use the after method to set the ball movement interval in milliseconds
# 10ms corresponds to approximately 100 FPS
root.after(10, move_ball)


# Start the ball movement
# Note: add a short delay to wait for window initialization, so canvas dimensions are correct
root.after(100, move_ball)

# Start main loop
root.mainloop()

Note: While the ball bounces as expected, you may notice visual stuttering or flickering. Tkinter is designed for desktop forms and business interfaces, not high-performance real-time rendering. For gaming or complex animations, specialized frameworks like Pygame are much better suited.