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:

  1. import tkinter as tk loads tkinter and lets the code call it tk for short
  2. tk.Tk() creates the main window, and title() and geometry() set its title and size
  3. tk.Label(window, ...) creates a label whose parent is the window, and pack() places it (pady=20 leaves 20 pixels above and below it)
  4. 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 and grid() inside a frame (one manager in each parent)
  • An Entry for input, with the Enter key bound to the same work as the Add button
  • A Listbox used 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

  1. Name Tag App (4 marks): Construct a tkinter application with an Entry, a Button labelled “Make Tag”, and a Label. 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”).

  2. Counter App (4 marks): Construct an application with a Label showing 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

  1. Reading Time Estimator (6 marks): Construct an app with an Entry for the number of pages in a book, a Combobox to choose a reading speed (“Slow: 20 pages an hour”, “Medium: 30 pages an hour” or “Fast: 45 pages an hour”), an “Estimate” button, and a Label that 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.

  2. 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

  1. Student Register (8 marks): Construct a form with boxes for a name, a grade (a Combobox with 11 and 12) and HL or SL (a Checkbutton). 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 a Listbox, below the students registered before. Add a “Clear Form” button that resets all the inputs to their defaults.

  2. 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 what mainloop() is doing while the user types, which method runs when Enter is pressed and why, and why handle_add() calls refresh_display() instead of updating the list itself. You are not required to write code.


Connections


© EduCS.me. A resource hub for Computer Science education. EduCS is an independent educational resource. It is not affiliated with, or endorsed by, the International Baccalaureate Organization.