Previous | Tutorial index | Next
Toplevel.Spinbox, OptionMenu).PanedWindow.Canvas and the .after() method.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:
Toplevel – for pop‑up windows, settings dialogs, and wizards.Spinbox – for precise numeric entry with step controls.OptionMenu – for elegant dropdown selections.PanedWindow – for creating resizable side‑by‑side panes (like an IDE).Canvas – a limitless drawing board for shapes, images, and custom graphics..after() – the secret sauce for animations, timers, and periodic tasks.These tools will allow you to build interfaces that feel polished, responsive, and feature‑rich.
Toplevel Widget – Multiple WindowsA 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:
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()
When you create a Toplevel, you usually pass the parent (root) as the first argument. This establishes a weak relationship:
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
.transient(parent) – tells the window manager this is a temporary window for the parent..grab_set() – prevents interaction with other windows in the application..wait_window(dialog) – pauses the program until the dialog is closed.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}")
.destroy() on the Toplevel object to close it.Spinbox Widget – Numeric Selection with StepsA Spinbox is an Entry widget with up/down arrow buttons that increment or decrement a value. It is useful for:
spin = tk.Spinbox(root, from_=0, to=100, increment=1, width=10)
spin.pack()
| 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. |
# 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
values for Non‑Numeric Optionscolors = ["Red", "Green", "Blue", "Yellow"]
spin = tk.Spinbox(root, values=colors, state="readonly")
spin.pack()
command Callback – Real‑time Updatesdef on_spin_change():
current = spin.get()
label.config(text=f"Volume: {current}")
spin = tk.Spinbox(root, from_=0, to=100, command=on_spin_change)
OptionMenu Widget – Dropdown SelectionAn 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.
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.
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()
OptionMenu DynamicallyYou 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.)
Spinbox – for numeric values with a small range, or when you want to type the value directly.OptionMenu – for categorical selections with a moderate number of options (2‑10).PanedWindow – Resizable PanesA 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:
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:
orient – tk.HORIZONTAL (side‑by‑side) or tk.VERTICAL (stacked top‑bottom).sashrelief – border style of the sash (RAISED, SUNKEN, etc.).sashwidth – thickness of the sash in pixels.handlepad / handlesize – for custom handles (optional)..add(child, minsize=width) – adds a pane with a minimum size (in pixels along the orientation)..panes() – returns a list of all child widgets..sash_place(index, position) – programmatically moves a sash (0‑based index)..sash_coord(index) – returns the current position of a sash.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)
Canvas Widget – Drawing and GraphicsA Canvas is a versatile widget that allows you to draw shapes, text, images, and even embed other widgets. It is the foundation for:
canvas = tk.Canvas(root, width=500, height=400, bg="white")
canvas.pack()
| 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:
fill – fill colour (e.g., "red", "#FF0000").outline – border colour.width – border thickness.dash – dash pattern (e.g., (5, 5) for dashed lines).tags – a string or tuple of tags for grouping items.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
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:
.coords(item, x1, y1, x2, y2) – get/set item coordinates..move(item, dx, dy) – relative movement..tag_bind(tag, event, callback) – bind events to all items with that tag..scale(item, x_origin, y_origin, x_scale, y_scale) – resize an item.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)
.after() Method – Timers and Animations.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.
def say_hello():
print("Hello after 2 seconds!")
root.after(2000, say_hello) # 2000 ms = 2 seconds
def greet(name):
print(f"Hello, {name}!")
root.after(1000, greet, "Alice")
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
.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)
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)
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.
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()
Toplevel window modal (blocking interaction with the parent)?
dialog.set_modal()dialog.transient(root) and dialog.grab_set()dialog.focus_force()dialog.block()values option in a Spinbox?
Canvas?
canvas.clear()canvas.delete("all")canvas.remove_all()canvas.empty()time.sleep()widget.after()widget.schedule()root.delay()PanedWindow and a normal Frame?
PanedWindow can only hold two children.PanedWindow has a draggable sash that lets users resize the panes.PanedWindow is not a container.PanedWindow cannot be nested.Toplevel window is automatically destroyed when the root window is destroyed.Spinbox widget cannot be used with non‑numeric strings.OptionMenu dynamically updates its options when the linked StringVar changes.Canvas items can be assigned tags, which allow you to manipulate multiple items at once..after() method blocks the entire GUI until the delay finishes.PanedWindow, you use the .______() method..______()..after() call, you use .after________().OptionMenu is constructed by passing an OptionMenu object, a StringVar, and then the options prefixed with * (called ________ unpacking)."________".Create a modal Toplevel that contains:
Spinbox for "Font Size" (from 8 to 30, default 12).OptionMenu for "Theme" (Light, Dark, Blue).Build a two‑pane window (horizontal):
Listbox containing a list of items (e.g., "Item 1" to "Item 10").Text widget that displays "Details for Item X" when an item is selected in the listbox (use listbox.bind("<<ListboxSelect>>", callback)).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.
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.
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.
Build a colour picker that allows the user to specify a colour by:
Scale widgets (or Spinbox widgets) for Red, Green, Blue values (0‑255).Canvas rectangle that updates its fill colour in real‑time as the sliders change.OptionMenu with a list of common colours (e.g., "Red", "Green", "Blue", "Yellow", "Purple", "Orange", "Black", "White"). When selected, the RGB sliders should update to match the chosen colour and the preview should update.Label that shows the hex code (e.g., #FF00AA) of the current colour, and an Entry that allows the user to type a hex code (which also updates the sliders and preview).Toplevel window that appears when a "Open Colour Picker" button is clicked on the main window. When the user closes the Toplevel, print the final selected colour in hex to the console.Layout Requirements:
PanedWindow to split the left side (controls) from the right side (preview).grid() to arrange the RGB scales, hex entry, and presets neatly.Canvas with the colour preview.Technical Requirements:
.trace("w") on the IntVars linked to the scales to update the preview.# and 6 hex digits) and update the sliders if valid. Use validatecommand on the Entry.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()
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)
Canvas Tag Binding (5 points)Write a short explanation (5‑7 sentences) of the tag_bind() method on a Canvas. Include:
tag_bind and binding to the canvas itself."current" tag works.tag_bind to change an item's colour on mouse hover.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.
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.
| 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. |
create_polygon, create_arc, and create_window (to embed widgets).after Method: Look into using after_idle for low‑priority tasks..after() is used to create real‑time games.Canvas using create_line and create_oval.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.