Tkinter Menu Bar

Deep dive · part of Python Tkinter

A real desktop app needs a menu bar. Build one with tk.Menu objects, attach commands and accelerators, and parent the top-level menu to the root window via root.config(menu=...).

Desktop apps expect File/Edit/Help menus built from tk.Menu cascades attached via root.config(menu=menubar). Commands bind Python callables; accelerators display shortcuts but require separate bind_all for key handling.

tearoff=False keeps menus attached on Windows/Linux; macOS may integrate differently—test platform targets.

Separators and cascaded submenus mirror desktop conventions users already understand, reducing training cost for internal utilities.

Separators and cascaded submenus mirror desktop conventions users already understand, reducing training cost for internal utilities.

Production code combines this topic with logging, tests, and clear module boundaries so refactors stay safe when requirements grow.

Menu(menubar, tearoff=False) creates dropdown; add_cascade labels top menu.

add_command(label=, command=, accelerator=) wires actions.

add_separator() groups related commands visually.

bind_all('', handler) implements accelerators globally.

messagebox module shows native About/alert dialogs.

Submenus are Menu instances passed to add_cascade(menu=sub).

Disable items with entryconfig(state='disabled') when actions invalid. Context menus (right-click) use tk.Menu posted with post(x, y) on events.

Recent files lists dynamically rebuild submenu on open—clear and repopulate to avoid stale commands.

Verify accelerators against OS reserved shortcuts on macOS python.org builds before shipping internal tools.

Disable menu entries with entryconfig when actions are invalid instead of showing errors after click.

Read the parent tutorial on pythondeck.com for runnable snippets, then reproduce them locally in a virtual environment with pinned dependency versions matching your deployment target.

When pairing with teammates, agree on one idiomatic pattern per concern—mixed styles in one repo slow reviews and invite subtle integration bugs during merges.

Accelerator strings without bind_all—menu shows shortcut but it does nothing.

Capturing parent menu wrong so menu bar does not appear.

Long-running command callbacks freezing UI—use threading or after() for work.

Non-ASCII menu labels without UTF-8 source declaration on older setups.

Map standard shortcuts consistently (Ctrl+O open, Ctrl+S save).

Separate menu construction into build_menubar(root) function.

Confirm quit destroys root and exits mainloop cleanly.

Test menus on each target OS before shipping internal tools.

Re-read the examples below with these ideas in mind; change variable names and inputs to match your own project.

The program below demonstrates file / help menus. Read the comments on each line, run the code, then change names or values to see how the output shifts.

# Example: File / Help menus
# Run in the REPL or save as a .py file and execute with python.
import tkinter as tk
from tkinter import messagebox

root = tk.Tk(); root.title("Editor")

menubar = tk.Menu(root)

file_menu = tk.Menu(menubar, tearoff=False)
file_menu.add_command(label="New",  accelerator="Ctrl+N", command=lambda: print("new"))
file_menu.add_command(label="Open", accelerator="Ctrl+O", command=lambda: print("open"))
file_menu.add_separator()
file_menu.add_command(label="Quit", command=root.destroy)

help_menu = tk.Menu(menubar, tearoff=False)
help_menu.add_command(label="About",
    command=lambda: messagebox.showinfo("About", "PythonDeck Editor"))

menubar.add_cascade(label="File", menu=file_menu)
menubar.add_cascade(label="Help", menu=help_menu)
root.config(menu=menubar)

root.bind_all("<Control-n>", lambda e: print("shortcut new"))
root.mainloop()

This sample walks through menu cascade in a small, runnable script. Paste it into the REPL or save it as a .py file before you continue to the next block.

# Menu bar attaches to root via config(menu=...)
import tkinter as tk  # GUI
from tkinter import messagebox  # dialogs
root = tk.Tk(); root.title("Editor")  # window
bar = tk.Menu(root)  # top bar
file_m = tk.Menu(bar, tearoff=False)  # File menu
file_m.add_command(label="New", command=lambda: print("new"))  # item
file_m.add_separator()  # visual break
file_m.add_command(label="Quit", command=root.destroy)  # exit
help_m = tk.Menu(bar, tearoff=False)  # Help menu
help_m.add_command(label="About", command=lambda: messagebox.showinfo("About", "Demo"))  # modal
bar.add_cascade(label="File", menu=file_m)  # attach File
bar.add_cascade(label="Help", menu=help_m)  # attach Help
root.config(menu=bar)  # show bar
print("menu ready")  # headless

Here is a hands-on illustration of accelerator bind. Follow the inline comments first; only then execute the snippet and compare the result with what you expected.

# Accelerators display shortcut text; bind_all handles keys
import tkinter as tk  # tk
root = tk.Tk()  # root
menu = tk.Menu(root, tearoff=False)  # menu
menu.add_command(label="Save", accelerator="Ctrl+S")  # hint only
root.config(menu=menu)  # attach
def save(_=None): print("saved")  # handler
root.bind_all("<Control-s>", save)  # actual shortcut
print("bind active")  # confirmation

« back to Python Tkinter All tutorials