tkinter Basics
A tool for your IA, not an exam topic: tkinter is not part of the assessed syllabus, so its classes and methods are not examined. It is a toolkit (a set of ready-made Python classes) for building programs with windows, buttons and text boxes, and you can use it to build the product for your Internal Assessment in Python. Writing in Java? The Java version of this page is JavaFX Basics.
Overview
For your Internal Assessment you build a coded product that solves a problem of your own choosing, and you show it working in a short video. A graphical user interface (GUI) lets the people using your product click buttons and type into boxes, instead of typing commands into a terminal (a text-only window).
tkinter is the GUI toolkit that comes with Python: a set of ready-made classes for windows, buttons, text boxes and layouts (how those parts are arranged on the screen). This page covers the basics you need to start building a working interface: windows, widgets (the controls, such as buttons and text boxes) and event handling (making the program respond when the user clicks or types). Later pages cover MVC with tkinter and file persistence (keeping the data after the program closes).
Goal: By the end of this page, you should be able to create a window with widgets that respond to user interaction.
Check that your Python has tkinter. Run import tkinter in Python: no error means it is there. The python.org installers for Windows and macOS include it, and so does the Thonny editor’s own installer on those systems. Some installations leave it out: Python from Homebrew on a Mac needs the separate python-tk package (brew install python-tk), and on Linux tkinter is usually a separate package (for example python3-tk). If you see “No module named ‘_tkinter’”, install that package, or install Python from python.org.
Key Concepts
The Main Window and Its Widgets
A tkinter program is built from three parts:
| Part | Made with | What it is |
|---|---|---|
| Main window | tk.Tk() | The window itself. A program makes one. |
| Widgets | tk.Label, tk.Entry, tk.Button, and more | The controls inside the window. Each one is given its parent (the window, or a frame inside it) when it is made. |
| Layout | pack() or grid() | Places each widget in its parent. A widget that is never placed is not shown. |
Your First Window
import tkinter as tk
window = tk.Tk()
window.title("My First App")
window.geometry("400x300") # width x height, in pixels
greeting = tk.Label(window, text="Hello, tkinter!")
greeting.pack(pady=20)
window.mainloop()
What happens when this runs:
import tkinter as tkloads tkinter and lets the code call ittkfor shorttk.Tk()creates the main window, andtitle()andgeometry()set its title and sizetk.Label(window, ...)creates a label whose parent is the window, andpack()places it (pady=20leaves 20 pixels above and below it)window.mainloop()shows the window and starts the event loop: the loop that waits for clicks and key presses and runs the matching code. It keeps running until the user closes the window
Code written after mainloop() runs only once the window has closed. Build the whole window first, then call mainloop() as the last line.
Older tutorials use from tkinter import *, which brings every tkinter name into your program without the tk. in front. import tkinter as tk keeps it clear which names come from tkinter and which are your own.
Writing the Window as a Class
An IA product soon has more than a few widgets, so it helps to write the window as a class. The constructor builds the widgets and keeps the ones it needs later as attributes; each button’s job becomes a method.
import tkinter as tk
class HelloApp():
def __init__(self, window):
window.title("My First App")
window.geometry("400x300")
self._greeting = tk.Label(window, text="Hello, tkinter!")
self._greeting.pack(pady=20)
self._button = tk.Button(window, text="Say hi", command=self.say_hi)
self._button.pack()
def say_hi(self):
self._greeting.config(text="Hi there!")
window = tk.Tk()
app = HelloApp(window)
window.mainloop()
Clicking Say hi runs say_hi(), which changes the label’s text with config(). The rest of this page uses this shape.
Common Widgets
Each short snippet (piece of code) below is a separate example of a few lines, written as if it were inside a window class’s __init__, so window is the parent. The complete program under Putting It Together shows how the pieces fit into one working app.
Label
Shows text that the user cannot edit.
result = tk.Label(window, text="Welcome")
result.pack()
result.config(text="Student added") # change the text later
Entry
A one-line box the user types into.
name_entry = tk.Entry(window, width=30)
name_entry.pack()
name = name_entry.get().strip() # what the user typed, without spaces at the ends
name_entry.delete(0, tk.END) # clear the box
get() always returns a string. To use the text as a number, convert it with int() or float() (see Input Validation).
Button
add_button = tk.Button(window, text="Add", command=self.handle_add)
add_button.pack()
command=self.handle_add gives the button the class’s handle_add method to run on each click. Write the name without brackets: command=self.handle_add() would run the method once, while the window is being built, and give the button its result instead.
Text
A box of several lines, for longer notes. Positions in a Text box are written as "line.character", so "1.0" is the start of the first line.
notes = tk.Text(window, width=40, height=5)
notes.pack()
notes.insert(tk.END, "First line\n") # add text at the end
text = notes.get("1.0", tk.END) # all the text
notes.delete("1.0", tk.END) # clear it
To show a list of items, a tk.Listbox is usually simpler: insert(tk.END, item) adds a line and delete(0, tk.END) clears them all. The complete program below uses one.
Combobox
A drop-down list. It is in tkinter.ttk, a part of tkinter with more modern-looking widgets.
from tkinter import ttk
grade_box = ttk.Combobox(window, values=["11", "12"], state="readonly")
grade_box.pack()
grade_box.set("11") # choose an item from the code
grade = grade_box.get() # the chosen item, as a string
state="readonly" lets the user choose from the list but not type their own value.
Checkbutton
A tick box. Its value lives in a tkinter variable object, here a tk.BooleanVar, which holds True or False and updates when the box is ticked.
self._hl_choice = tk.BooleanVar() # an attribute, so it lasts after __init__
hl_check = tk.Checkbutton(window, text="HL", variable=self._hl_choice)
hl_check.pack()
# later, in a handler such as handle_register:
if self._hl_choice.get() == True:
print("HL is ticked")
Keep a tkinter variable as an attribute. A BooleanVar or StringVar kept only in a local variable is deleted when __init__ ends, and the tick box loses track of its value.
Layout
A layout manager decides where each widget goes in its parent. tkinter has two you will use most.
pack: One After Another
pack() stacks widgets from the top down, in the order they are packed. padx and pady add space around a widget.
tk.Label(window, text="Name").pack(pady=4)
tk.Entry(window).pack(pady=4)
tk.Button(window, text="Save").pack(pady=4)
grid: Rows and Columns
grid() places each widget in a row and a column, counting from 0. columnspan lets a widget cover more than one column, and sticky="w" lines it up on the left (west) side of its cell.
tk.Label(window, text="Name").grid(row=0, column=0, sticky="w")
tk.Entry(window).grid(row=0, column=1)
tk.Label(window, text="Grade").grid(row=1, column=0, sticky="w")
tk.Entry(window).grid(row=1, column=1)
tk.Button(window, text="Save").grid(row=2, column=0, columnspan=2)
Frame: Groups of Widgets
A tk.Frame is an empty box that holds other widgets. Use frames to build a window in parts: pack the frames into the window, then arrange the widgets inside each frame.
form = tk.Frame(window)
form.pack(padx=10, pady=10)
tk.Label(form, text="Name").grid(row=0, column=0)
tk.Entry(form).grid(row=0, column=1)
Use one layout manager in each parent. Inside one window or one frame, place every widget with pack() or every widget with grid(). With the default layout settings, current versions of tkinter stop the program with an error that starts cannot use geometry manager when the two are mixed in the same parent. A frame can use a different manager from the window it sits in, as the example above does.
Event Handling
An event is something the program can respond to, usually an action by the user such as clicking a button, pressing Enter or choosing an item from a list. The code that responds to an event is its event handler. A GUI program is event-driven: once the window is built and mainloop() is running, the program waits, and each event handler runs when its event happens. The order of the user’s clicks decides the order in which your code runs.
Buttons: command
A button’s handler is the method given to command, as above. It is called with no arguments.
Keys and Other Events: bind
Other events are connected with bind(). The handler is given one argument, an event object that describes what happened, so its method takes a parameter for it even when it does not use it.
def handle_enter(self, event): # a key binding passes an event object
self.handle_add()
# in __init__:
self._name_entry.bind("<Return>", self.handle_enter) # Enter key in the box
A Combobox reports a new choice with the event "<<ComboboxSelected>>":
def handle_grade(self, event):
self._result.config(text="Selected: " + self._grade_box.get())
# in __init__:
self._grade_box.bind("<<ComboboxSelected>>", self.handle_grade)
Passing a value to a handler. command calls its method with no arguments. When several buttons share one method with different values, tutorials often write command=lambda: self.choose("S"). A lambda is a short function with no name; here it calls choose("S") when the button is clicked. Lambdas are not part of the Python that the syllabus examines, so treat them as a tool for your IA, or write a small method for each button instead.
Keep event handlers short. When a handler grows beyond a few lines, move the work into a separate method and call it, as the complete program below does with refresh_display(). Short handlers are easier to read and to change.
Message Boxes
A message box is a small pop-up window that shows a message, asks the user to confirm something, or reports an error. It pauses your code until the user closes it.
from tkinter import messagebox
messagebox.showinfo("Success", "Student added successfully.")
messagebox.showerror("Error", "Name cannot be empty.")
if messagebox.askyesno("Confirm Delete", "Delete this student? This cannot be undone.") == True:
# the user clicked Yes, so delete the student here
print("Deleted")
askyesno() returns True when the user clicks Yes and False when they click No.
Use message boxes sparingly. When the user’s input needs fixing (for example, “name is required”), a message in a label is often better than a pop-up. Keep message boxes for confirmations and serious errors that the user must not miss.
Putting It Together
Here is a complete small application that lets the user add names to a list and see the list on screen.
import tkinter as tk
from tkinter import messagebox
class NameListApp():
def __init__(self, window):
self._names = []
window.title("Name List Manager")
tk.Label(window, text="Name List Manager").pack(pady=(10, 0))
self._display = tk.Listbox(window, width=40, height=10)
self._display.pack(padx=10, pady=10)
form = tk.Frame(window)
form.pack(padx=10, pady=(0, 10))
self._name_entry = tk.Entry(form, width=30)
self._name_entry.grid(row=0, column=0, columnspan=2, pady=4)
tk.Button(form, text="Add", command=self.handle_add).grid(row=1, column=0, pady=4)
tk.Button(form, text="Clear All", command=self.handle_clear).grid(row=1, column=1, pady=4)
self._status = tk.Label(form, text="")
self._status.grid(row=2, column=0, columnspan=2)
self._name_entry.bind("<Return>", self.handle_enter)
def handle_add(self):
name = self._name_entry.get().strip()
if name == "":
self._status.config(text="Please enter a name.")
else:
self._names.append(name)
self._name_entry.delete(0, tk.END)
self._status.config(text="Added: " + name)
self.refresh_display()
def handle_enter(self, event): # a key binding passes an event object
self.handle_add()
def handle_clear(self):
if messagebox.askyesno("Confirm", "Clear all names?") == True:
self._names = []
self._status.config(text="List cleared.")
self.refresh_display()
def refresh_display(self):
self._display.delete(0, tk.END)
for i in range(len(self._names)):
self._display.insert(tk.END, str(i + 1) + ". " + self._names[i])
window = tk.Tk()
app = NameListApp(window)
window.mainloop()
What this demonstrates:
- A window written as a class, with the widgets it needs later kept as attributes
pack()in the window andgrid()inside a frame (one manager in each parent)- An
Entryfor input, with the Enter key bound to the same work as the Add button - A
Listboxused as a read-only display - A message box that asks the user to confirm before all the names are deleted
- A list to hold the data (the names are lost when the window closes; File I/O in Apps shows how to save them to a file and load them again)
- A separate method,
refresh_display(), that updates the display and is called by both handlers
Common Patterns for IA Projects
Input Validation
Input validation means checking the user’s input before you use it. This version of handle_add from the complete program adds a length check:
def handle_add(self):
name = self._name_entry.get().strip()
if name == "":
self._status.config(text="Error: Name cannot be empty.")
elif len(name) > 50:
self._status.config(text="Error: Name too long (max 50 characters).")
else:
# the input is valid, so add the name
self._names.append(name)
self._name_entry.delete(0, tk.END)
self._status.config(text="Added: " + name)
self.refresh_display()
To check that typed text is a number, convert it with int() or float() inside a try block. If the text is not a number, the conversion raises a ValueError, which your except block can handle by showing a message, as on the Exception Handling page.
Disabling Widgets
You can disable a button (grey it out so that it cannot be clicked) while its action would make no sense, for example Add while the name box is empty. A tk.StringVar connected to the box can call a method every time the text changes, however it changes (typing, pasting, or the program clearing the box):
def update_add_button(self, name, index, mode): # tkinter passes three values; this method does not need them
if self._name_text.get().strip() == "":
self._add_button.config(state="disabled")
else:
self._add_button.config(state="normal")
# in __init__:
self._name_text = tk.StringVar()
self._name_entry = tk.Entry(window, textvariable=self._name_text)
self._add_button = tk.Button(window, text="Add", command=self.handle_add, state="disabled")
self._name_text.trace_add("write", self.update_add_button) # run it whenever the text changes
Keep the check in handle_add() as well: a disabled button is a help for the user, and the handler still makes sure the input can be used.
Updating a Status Label
A status label at the bottom of the window tells the user what happened, without a pop-up:
self._status.config(text="Ready") # default state
self._status.config(text="Added: " + name) # after a successful action
self._status.config(text="Error: invalid input") # on a validation failure
Colours
Most widgets take bg (background) and fg (text colour), as a colour name or a hex code:
tk.Label(window, text="Saved", bg="#E6F4EA", fg="#1E4620").pack()
On a Mac, tk.Button usually keeps the system’s own button look and may ignore bg; labels and frames take the colour.
Quick Check
Q1. Which line in a tkinter program shows the window and starts waiting for clicks and key presses?
Q2. A student creates a label with its parent set to the window, but the label never appears. The program runs without an error. What is the most likely cause?
Q3. A window places its title with pack() and its form with grid(), all in the window itself, and the program stops with an error. What is the best fix?
Q4. A user types 42 into an Entry box. What does the box's get() method return?
Q5. A student adds a new button in a line written after window.mainloop(). When does that line run?
Practice Exercises
Core
-
Name Tag App (4 marks): Construct a tkinter application with an
Entry, aButtonlabelled “Make Tag”, and aLabel. When the button is clicked, the label should show the name typed in the box in capital letters, without any spaces before or after it (for example, “ mia “ becomes “MIA”). -
Counter App (4 marks): Construct an application with a
Labelshowing a number (starting at 0) and two buttons: “+” and “-“. Clicking “+” increments the number, and clicking “-“ decrements it, so one click on “-“ at 0 shows -1. The label should update after each click.
Extension
-
Reading Time Estimator (6 marks): Construct an app with an
Entryfor the number of pages in a book, aComboboxto choose a reading speed (“Slow: 20 pages an hour”, “Medium: 30 pages an hour” or “Fast: 45 pages an hour”), an “Estimate” button, and aLabelthat shows the reading time in hours and minutes (for example, 250 pages at the fast speed takes about 5 hours 33 minutes). Before estimating, check that the input is a whole number from 1 to 5000, and show a message in the label if it is not. -
Trip Sign-up List (6 marks): Construct an app for a class trip with 20 places. The user types a name and clicks “Sign Up” to add it to a list, which is shown as a numbered list in a
Listbox. Refuse an empty name, a name that is already on the list (even with different capital letters) and any sign-up once all 20 places are taken, with a message in a status label each time. Add a “Remove Last” button that asks for confirmation before removing the most recent name.
Challenge
-
Student Register (8 marks): Construct a form with boxes for a name, a grade (a
Comboboxwith 11 and 12) and HL or SL (aCheckbutton). Add a “Register” button that checks that a name has been typed and a grade chosen, and adds the student’s information as a new line in aListbox, below the students registered before. Add a “Clear Form” button that resets all the inputs to their defaults. -
Describe the Order of Events (6 marks): In the Name List Manager from Putting It Together, the user starts the app, types “Mia” and presses Enter. Describe, in order, what the program does from the moment
NameListApp(window)is called until “1. Mia” appears in the list. Include whatmainloop()is doing while the user types, which method runs when Enter is pressed and why, and whyhandle_add()callsrefresh_display()instead of updating the list itself. You are not required to write code.
Connections
- Prerequisites: Classes and Objects: the window is written as a class with a constructor and methods
- Prerequisites: Exception Handling:
tryandexceptfor turning typed text into numbers - Java version: JavaFX Basics: the same ideas with JavaFX
- Next: MVC with tkinter: separating the data, the window and the code that responds to the user
- Next: File I/O in Apps: saving and loading data