Previous | Tutorial index | Next

Tutorial 9: Advanced Tkinter Widgets and Features

Learning Objectives

1. Introduction – Beyond the Basics

Congratulations on making it to the advanced tutorial! By now, you can build forms, handle events, and even create a fully functional text editor. But Tkinter has more powerful tools hidden up its sleeve.

In this tutorial, we will cover specialised widgets that are essential for professional applications:

These tools will allow you to build interfaces that feel polished, responsive, and feature‑rich.

2. The Toplevel Widget – Multiple Windows

2.1 What Is a Toplevel?

A Toplevel is a secondary window that is independent of the main application window (root). Unlike a Tk instance (of which you should have only one), you can create as many Toplevel windows as you need. They are perfect for:

2.2 Basic Usage

import tkinter as tk root = tk.Tk() root.title("Main Window") def open_settings(): settings_win = tk.Toplevel(root) settings_win.title("Settings") settings_win.geometry("300x200") tk.Label(settings_win, text="Adjust your preferences here").pack() btn = tk.Button(root, text="Open Settings", command=open_settings) btn.pack() root.mainloop()

2.3 Parent–Child Relationship

When you create a Toplevel, you usually pass the parent (root) as the first argument. This establishes a weak relationship:

2.4 Modality – Blocking the Parent

Sometimes you want the user to focus on the child window before they can return to the parent. This is called a modal dialog.

How to make a modal dialog:

def open_modal(): dialog = tk.Toplevel(root) dialog.title("Modal Dialog") dialog.geometry("250x100") tk.Label(dialog, text="This is modal. Close me first.").pack() # Make it modal: dialog.transient(root) # Associate with parent dialog.grab_set() # Grab all events (mouse/keyboard) dialog.focus_set() # Focus on the dialog root.wait_window(dialog) # Wait until the dialog is destroyed

2.5 Passing Data Back from a Toplevel

Often you need to retrieve user input from a pop‑up. You can store the result in a variable and read it after wait_window returns:

def get_user_input(): result = tk.StringVar() dialog = tk.Toplevel(root) dialog.title("Enter Name") entry = tk.Entry(dialog, textvariable=result) entry.pack(padx=10, pady=10) def on_ok(): dialog.destroy() tk.Button(dialog, text="OK", command=on_ok).pack() dialog.transient(root) dialog.grab_set() root.wait_window(dialog) return result.get() # Usage: name = get_user_input() print(f"User entered: {name}")

2.6 Destroying a Toplevel

3. The Spinbox Widget – Numeric Selection with Steps

3.1 What Is a Spinbox?

A Spinbox is an Entry widget with up/down arrow buttons that increment or decrement a value. It is useful for:

3.2 Basic Usage

spin = tk.Spinbox(root, from_=0, to=100, increment=1, width=10) spin.pack()

3.3 Key Options

Option Description
from_ / to The minimum and maximum values (note the trailing underscore because from is a keyword).
increment The step size (e.g., 0.5 for decimals).
values A tuple/list of fixed values (e.g., ("one", "two", "three")). If this is set, from_/to are ignored.
wrap If True, wraps from max to min when clicking up at the max.
command A callback function that is called whenever the spinbox value changes.
textvariable A StringVar or IntVar linked to the current value.
state "normal" or "readonly" (prevents manual typing).
width Width in characters.

3.4 Getting and Setting Values

# Get current value val = spin.get() # Set a new value (must be within range) spin.delete(0, tk.END) spin.insert(0, "50") # Or use textvariable: var = tk.IntVar() spin = tk.Spinbox(root, textvariable=var, from_=0, to=100) var.set(25) # Updates spinbox

3.5 Using values for Non‑Numeric Options

colors = ["Red", "Green", "Blue", "Yellow"] spin = tk.Spinbox(root, values=colors, state="readonly") spin.pack()

3.6 The command Callback – Real‑time Updates

def on_spin_change(): current = spin.get() label.config(text=f"Volume: {current}") spin = tk.Spinbox(root, from_=0, to=100, command=on_spin_change)

4. The OptionMenu Widget – Dropdown Selection

4.1 What Is an OptionMenu?

An OptionMenu is a dropdown menu that lets the user select one option from a list. It is a more modern and compact alternative to a group of Radiobutton widgets.

4.2 Basic Usage

options = ["Apple", "Banana", "Cherry", "Date"] var = tk.StringVar() var.set(options[0]) # Set default dropdown = tk.OptionMenu(root, var, *options) dropdown.pack() # To get selected value: selected = var.get()

Note: The *options syntax unpacks the list as separate arguments.

4.3 Using a Dictionary for More Control

You can also use a dictionary where the keys are the displayed text and the values are the underlying data:

option_dict = {"Option A": "A", "Option B": "B", "Option C": "C"} var = tk.StringVar() var.set("A") menu = tk.OptionMenu(root, var, *option_dict.values()) menu.pack()

4.4 Updating the OptionMenu Dynamically

You can change the options after creation by using the menu attribute:

def update_options(new_options): menu = dropdown["menu"] menu.delete(0, tk.END) # Clear all items for item in new_options: menu.add_command(label=item, command=tk._setit(var, item)) # Usage: update_options(["X", "Y", "Z"])

(Note: tk._setit is a helper that sets the variable when the item is selected.)

4.5 When to Use Which?

5. The PanedWindow – Resizable Panes

5.1 What Is a PanedWindow?

A PanedWindow is a container that holds multiple child widgets (panes) separated by a sash (a draggable divider). The user can drag the sash to resize the panes. It is ideal for:

5.2 Basic Usage

paned = tk.PanedWindow(root, orient=tk.HORIZONTAL, sashrelief=tk.RAISED, sashwidth=5) left = tk.Label(paned, text="Left Pane", bg="lightblue") right = tk.Label(paned, text="Right Pane", bg="lightgreen") paned.add(left, minsize=100) paned.add(right, minsize=100) paned.pack(fill=tk.BOTH, expand=True)

Key Options:

5.3 Adding and Managing Panes

5.4 Nesting PanedWindows

You can nest PanedWindow instances inside each other to create complex layouts (e.g., left‑right split, then top‑bottom split inside the right pane):

main_pane = tk.PanedWindow(root, orient=tk.HORIZONTAL) main_pane.pack(fill=tk.BOTH, expand=True) left_pane = tk.Label(main_pane, text="Left", bg="lightblue", width=200) right_pane = tk.PanedWindow(main_pane, orient=tk.VERTICAL) top_right = tk.Label(right_pane, text="Top Right", bg="lightgreen") bottom_right = tk.Label(right_pane, text="Bottom Right", bg="lightyellow") right_pane.add(top_right) right_pane.add(bottom_right) main_pane.add(left_pane, minsize=150) main_pane.add(right_pane, minsize=200)

6. The Canvas Widget – Drawing and Graphics

6.1 What Is a Canvas?

A Canvas is a versatile widget that allows you to draw shapes, text, images, and even embed other widgets. It is the foundation for:

6.2 Basic Setup

canvas = tk.Canvas(root, width=500, height=400, bg="white") canvas.pack()

6.3 Drawing Primitives – Methods and Return Values

Method Description Returns
create_line(x1,y1,x2,y2, options) Draws a line segment. Item ID (int)
create_rectangle(x1,y1,x2,y2, options) Draws a rectangle. Item ID
create_oval(x1,y1,x2,y2, options) Draws an oval/circle within the bounding box. Item ID
create_polygon(x1,y1,x2,y2,..., options) Draws a polygon (list of points). Item ID
create_arc(x1,y1,x2,y2, options) Draws an arc (pie slice or segment). Item ID
create_text(x,y, text, options) Places text on the canvas. Item ID
create_image(x,y, image, options) Displays an image (PhotoImage). Item ID

Common Options for Shapes:

6.4 Example – Drawing a House

canvas.create_rectangle(50, 150, 250, 300, fill="lightyellow", outline="black") canvas.create_polygon(50, 150, 150, 50, 250, 150, fill="brown", outline="black") canvas.create_rectangle(120, 220, 180, 300, fill="blue", outline="black") # Door canvas.create_oval(70, 180, 100, 210, fill="white", outline="black") # Window

6.5 Manipulating Canvas Items – ID and Tags

Each item created returns a unique item ID. You can also assign tags to groups.

rect = canvas.create_rectangle(10, 10, 50, 50, fill="red", tags=("shape", "moving")) canvas.itemconfig(rect, fill="blue") # Change colour canvas.move(rect, 10, 5) # Move by dx, dy canvas.delete(rect) # Remove item # Using tags: canvas.itemconfig("moving", fill="green") # All items with tag "moving" canvas.delete("moving") # Delete all items with that tag

Key Manipulation Methods:

6.6 Responding to Canvas Events

You can bind mouse events to canvas items using tags:

def on_click(event): print(f"Clicked at ({event.x}, {event.y})") # Get the clicked item: item = canvas.find_withtag("current") # "current" is a special tag for the item under the mouse if item: canvas.itemconfig(item, fill="red") canvas.tag_bind("shape", "<Button-1>", on_click)

7. The .after() Method – Timers and Animations

7.1 What Is .after()?

.after(delay_ms, callback, *args) is a method available on all Tkinter widgets (but commonly used on the root window). It schedules callback to be called after delay_ms milliseconds. Unlike time.sleep(), .after() does not block the event loop—it is non‑blocking, which is essential for GUI responsiveness.

7.2 Basic Usage

def say_hello(): print("Hello after 2 seconds!") root.after(2000, say_hello) # 2000 ms = 2 seconds

7.3 Passing Arguments

def greet(name): print(f"Hello, {name}!") root.after(1000, greet, "Alice")

7.4 Creating a Timer – Counting Down

def countdown(seconds): if seconds > 0: label.config(text=f"Time left: {seconds}") root.after(1000, countdown, seconds - 1) else: label.config(text="Time's up!") root.after(0, countdown, 10) # Start a 10‑second countdown

7.5 Cancelling a Scheduled Call – .after_cancel()

The .after() method returns an ID that you can use to cancel the pending call:

job_id = root.after(5000, some_function) # Later: root.after_cancel(job_id)

7.6 Creating Simple Animations – Moving a Ball

def move_ball(): canvas.move(ball, 5, 0) coords = canvas.coords(ball) if coords[2] < 500: # Check if still within canvas width root.after(50, move_ball) else: canvas.move(ball, -500, 0) # Reset to left ball = canvas.create_oval(0, 100, 30, 130, fill="red") root.after(100, move_ball)

7.7 Periodic Updates – Clock or Dashboard

Use .after() with a self‑scheduling function to create a repeating timer:

def update_clock(): import time current = time.strftime("%H:%M:%S") clock_label.config(text=current) root.after(1000, update_clock) # Update every second update_clock()

Important: Always call .after() again inside the callback to keep the loop running.

8. Putting It All Together – A Simple Drawing App with Timer

Let's combine Canvas, .after(), OptionMenu, and Spinbox to build a small drawing app that also has an auto‑draw feature.

import tkinter as tk from random import randint class DrawingApp: def __init__(self, root): self.root = root self.root.title("Advanced Drawing App") # Canvas self.canvas = tk.Canvas(root, width=500, height=400, bg="white") self.canvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True) # Control Panel (using PanedWindow to show example) control_pane = tk.PanedWindow(root, orient=tk.HORIZONTAL) control_pane.pack(side=tk.BOTTOM, fill=tk.X) # Shape Selector (OptionMenu) shapes = ["Rectangle", "Oval", "Line"] self.shape_var = tk.StringVar(value=shapes[0]) shape_menu = tk.OptionMenu(control_pane, self.shape_var, *shapes) control_pane.add(shape_menu) # Size Selector (Spinbox) self.size_var = tk.IntVar(value=20) size_spin = tk.Spinbox(control_pane, from_=5, to=50, textvariable=self.size_var) control_pane.add(size_spin) # Buttons btn_frame = tk.Frame(control_pane) control_pane.add(btn_frame) tk.Button(btn_frame, text="Draw Random", command=self.draw_random).pack(side=tk.LEFT) tk.Button(btn_frame, text="Clear", command=self.clear_canvas).pack(side=tk.LEFT) tk.Button(btn_frame, text="Animate", command=self.start_animation).pack(side=tk.LEFT) tk.Button(btn_frame, text="Stop", command=self.stop_animation).pack(side=tk.LEFT) self.animation_id = None self.animation_objects = [] def draw_random(self): """Draw a random shape at a random position.""" x = randint(10, 490) y = randint(10, 390) size = self.size_var.get() shape = self.shape_var.get() if shape == "Rectangle": self.canvas.create_rectangle(x, y, x+size, y+size, fill="blue", outline="black") elif shape == "Oval": self.canvas.create_oval(x, y, x+size, y+size, fill="green", outline="black") elif shape == "Line": self.canvas.create_line(x, y, x+size, y+size, fill="red", width=3) def clear_canvas(self): self.canvas.delete("all") self.animation_objects.clear() if self.animation_id: self.root.after_cancel(self.animation_id) self.animation_id = None def start_animation(self): """Animate newly drawn shapes by moving them to the right.""" if self.animation_id: return # Already running self.animation_objects = [] # Create 10 circles for i in range(10): obj = self.canvas.create_oval(20+i*40, 50, 50+i*40, 80, fill="orange") self.animation_objects.append(obj) self.animate_step() def animate_step(self): """Move all animated objects by +5 in x.""" if not self.animation_objects: return for obj in self.animation_objects: self.canvas.move(obj, 5, 0) # Reset after they go off screen (simplistic) coords = self.canvas.coords(self.animation_objects[0]) if coords and coords[0] > 500: for obj in self.animation_objects: self.canvas.move(obj, -500, 0) self.animation_id = self.root.after(50, self.animate_step) def stop_animation(self): if self.animation_id: self.root.after_cancel(self.animation_id) self.animation_id = None if __name__ == "__main__": root = tk.Tk() app = DrawingApp(root) root.mainloop()

9. Check Your Understanding (Quizzes)

Quiz 1: Multiple Choice

  1. Which method is used to make a Toplevel window modal (blocking interaction with the parent)?
AnswerB – `transient` + `grab_set` is the modal combination.
  1. What is the purpose of the values option in a Spinbox?
AnswerB – `values` provides a fixed list of choices.
  1. How do you remove all items from a Canvas?
AnswerB – `delete("all")` is the correct syntax.
  1. Which method schedules a function to run after a delay without blocking the main loop?
AnswerB – `.after()` is non‑blocking.
  1. What is the primary difference between PanedWindow and a normal Frame?
AnswerB – The sash makes it resizable by the user.

Quiz 2: True or False

  1. True / False: A Toplevel window is automatically destroyed when the root window is destroyed.
AnswerTrue – Toplevels are children of the parent.
  1. True / False: The Spinbox widget cannot be used with non‑numeric strings.
AnswerFalse – You can use `values=("One", "Two", "Three")`.
  1. True / False: The OptionMenu dynamically updates its options when the linked StringVar changes.
AnswerFalse – The menu items are not automatically updated; you must clear and repopulate them.
  1. True / False: Canvas items can be assigned tags, which allow you to manipulate multiple items at once.
AnswerTrue – Tags are powerful for grouping.
  1. True / False: The .after() method blocks the entire GUI until the delay finishes.
AnswerFalse – `.after()` is non‑blocking.

Quiz 3: Fill in the Blanks

  1. To add a child pane to a PanedWindow, you use the .______() method.
Answeradd
  1. The canvas method to move an item by (dx, dy) is .______().
Answermove
  1. To cancel a scheduled .after() call, you use .after________().
Answercancel
  1. The OptionMenu is constructed by passing an OptionMenu object, a StringVar, and then the options prefixed with * (called ________ unpacking).
Answerargument
  1. The special canvas tag that always refers to the item under the mouse cursor is "________".
Answercurrent

10. Hands‑On Lab (In‑Class Exercises)

Exercise 1: Modal Settings Dialog

Create a modal Toplevel that contains:

Sample Solution Outline Use `Toplevel`, `transient`, `grab_set`, `wait_window`. Store settings in variables, print on Apply, then `dialog.destroy()`.

Exercise 2: PanedWindow with Tree and Details

Build a two‑pane window (horizontal):

Sample Solution Outline - `paned = tk.PanedWindow(root, orient=tk.HORIZONTAL)` - `left = tk.Listbox(paned)` and `right = tk.Text(paned)` - `paned.add(left, minsize=150)` and `paned.add(right, minsize=150)` - Bind `<>` to update right pane.

Exercise 3: Simple Canvas Drawing Tool

Create a canvas and bind <B1-Motion> (drag with left mouse button) to draw a line that follows the mouse. Use create_line with the previous coordinates stored as instance variables. Also add a "Clear" button.

Sample Solution Outline - Store `self.last_x`, `self.last_y`; on `` set them; on `` draw line from last to current, update last. - Clear button: `canvas.delete("all")`.

Exercise 4: Animated Bouncing Ball

Create a canvas with a circle (ball). Use .after() to move the ball in a diagonal direction. When it hits the edge of the canvas, bounce it (reverse the direction). Use a speed variable and allow the user to change speed with a Spinbox.

Sample Solution Outline - Store `dx`, `dy` (e.g., 5,5). In update function, move ball, check if beyond canvas edges, reverse dx/dy. - Use Spinbox to change speed by adjusting the `after` delay or step size.

11. Homework Assignment

Objective

Demonstrate your ability to combine advanced Tkinter features to build a useful mini‑application. You will create a Custom Colour Picker with a live preview, using a Toplevel, Canvas, Spinbox, and OptionMenu.

Part A: The Colour Picker Application (20 points)

Build a colour picker that allows the user to specify a colour by:

Layout Requirements:

Technical Requirements:

Sample Solution Outline - Main window with a button to open picker. - Picker class: create Toplevel, make modal. - Use `PanedWindow` with left frame (controls) and right canvas (preview). - Left frame: grid layout with labels, scales for R,G,B (IntVar), OptionMenu for presets, Entry for hex with validation, and Copy button. - Trace on each IntVar to update preview and hex entry. - Preset selection sets the IntVars. - Hex validation: accept only `#` followed by 6 hex digits; on valid, parse and set IntVars. - Copy button uses clipboard. - On close, print current hex.

Part B: Code Analysis – Fix the Animation Bug (10 points)

The following code attempts to create an animation that moves a rectangle to the right and wraps around, but it has three logical bugs that cause it to behave incorrectly or crash. Identify each bug, explain the problem, and provide corrected code.

import tkinter as tk root = tk.Tk() canvas = tk.Canvas(root, width=300, height=200) canvas.pack() rect = canvas.create_rectangle(0, 0, 50, 50, fill="red") def animate(): canvas.move(rect, 5, 0) coords = canvas.coords(rect) if coords[0] > 300: # Bug 1 canvas.move(rect, -300, 0) # Bug 2 root.after(100, animate) # Bug 3 (not a bug in itself, but placement) animate() root.mainloop()
Answers 1. `coords[0] > 300` – checks the left edge, but the rectangle may have moved beyond the right edge; should check `coords[2] > 300` (right edge). 2. `canvas.move(rect, -300, 0)` – moves the rectangle back by 300, but this might not bring it exactly to the left edge; better to set coordinates explicitly or move by `-(self.canvas_width)`. 3. The `animate` function is called recursively without any stop condition; it will run forever, which is fine, but the bug is that after wrapping, the rectangle might not be fully visible; also, the `after` call is inside the function, but it's correct.

Corrected code:

def animate(): canvas.move(rect, 5, 0) coords = canvas.coords(rect) if coords[2] > 300: # right edge canvas.move(rect, -300, 0) root.after(100, animate)

Part C: Research – Canvas Tag Binding (5 points)

Write a short explanation (5‑7 sentences) of the tag_bind() method on a Canvas. Include:

Sample Answer `tag_bind()` attaches an event binding to all canvas items that have a specific tag. Unlike binding directly to the canvas (which responds to clicks anywhere on the canvas), `tag_bind` only triggers when the event occurs on an item with that tag. The special tag `"current"` refers to the item currently under the mouse cursor, which is useful for hover effects. For example, `canvas.tag_bind("shape", "", lambda e: canvas.itemconfig("current", fill="red"))` changes the colour of the shape under the mouse when it enters. This is more efficient than binding to individual item IDs.

Part D: Reflection – When to Use Toplevel vs Tk (5 points)

You have learned that you should only create one Tk() instance. Write a paragraph (5‑7 sentences) explaining why you should not create multiple Tk() windows, and why Toplevel is the proper alternative. Mention differences in mainloop() handling, resource management, and application structure.

Sample Answer Creating multiple `Tk()` instances creates separate event loops, which can lead to conflicts and unpredictable behaviour because each has its own `mainloop`. Additionally, multiple `Tk` instances share the same Tcl interpreter, causing resource contention. The proper way to create secondary windows is to use `Toplevel`, which is a child of the root `Tk` instance and shares the same event loop. This ensures that all windows are managed consistently and resources are handled efficiently. `Toplevel` also allows modal behaviour via `grab_set` and `transient`, which is not straightforward with multiple `Tk` windows. For a clean application structure, always have a single `Tk` root and use `Toplevel` for any additional windows.

Part E: Extra Challenge – Custom Colours with PanedWindow (Bonus: +5 points)

Enhance the Colour Picker from Part A: use a PanedWindow that allows the user to resize the preview pane horizontally. The left control pane should have a minimum width of 250 pixels.

Sample Solution Outline Replace the left/right frames with a `PanedWindow`; add the left frame with `minsize=250`, and the right canvas with `minsize=200`.

12. Summary of Key Terms (Glossary)

Term Definition
Toplevel A secondary, independent window that can be modal or non‑modal.
Modal Dialog A window that blocks interaction with its parent until closed.
Spinbox A numeric input with up/down arrows for incrementing/decrementing.
OptionMenu A dropdown selection widget that displays a list of options.
PanedWindow A container with draggable sashes that allow users to resize panes.
Sash The draggable divider between panes in a PanedWindow.
Canvas A versatile widget for drawing shapes, lines, images, and text.
Item ID A unique integer returned when a canvas item is created, used to reference it.
Tag A string or tuple attached to canvas items, allowing bulk operations and event binding.
.after() A non‑blocking timer method that schedules a callback after a delay.
.after_cancel() Cancels a previously scheduled .after() call using its ID.

13. Further Resources for Self‑Study

This tutorial is designed to take approximately 3 hours of study, lab work, and homework. Mastering these advanced widgets will allow you to build applications that are not only functional but also visually impressive and user‑friendly.

Previous | Tutorial index | Next