0% found this document useful (0 votes)
2 views40 pages

Tkinter Crash Course: Desktop Development With Python

This document is a comprehensive guide to using Tkinter for desktop development with Python, covering topics from basic GUI concepts to advanced widgets and multi-threading. It includes detailed sections on essential and advanced widgets, event handling, data persistence, and packaging applications for distribution. The guide also features a real-world project example, a full task manager app, to illustrate practical application of the concepts discussed.

Uploaded by

prahladyeri
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views40 pages

Tkinter Crash Course: Desktop Development With Python

This document is a comprehensive guide to using Tkinter for desktop development with Python, covering topics from basic GUI concepts to advanced widgets and multi-threading. It includes detailed sections on essential and advanced widgets, event handling, data persistence, and packaging applications for distribution. The guide also features a real-world project example, a full task manager app, to illustrate practical application of the concepts discussed.

Uploaded by

prahladyeri
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

Tkinter Crash Course: Desktop Development with Python

A comprehensive guide from basic GUIs to modern web-wrapper apps, multi-threading, and distribution.

Table of Contents
1. Introduction & Setup
2. Core Concepts: Windows, Geometry, and Layout
3. Essential Widgets
4. Advanced Widgets
5. Styling with ttk and Themes
6. Event Handling & Bindings
7. Dialogs, Menus, and System Tray
8. Multi-threading & Responsiveness
9. Data Persistence: SQLite, JSON, Config Files
10. Modern HTML Apps with Web Wrappers
11. Packaging & Distribution
12. Real-World Project: A Full Task Manager App

1. Introduction & Setup


Why Tkinter?
Tkinter is Python’s standard GUI library — it ships with CPython on all platforms
(Windows, macOS, Linux) with zero extra installation. It wraps Tcl/Tk and gives you:
• Zero dependencies for basic apps
• Cross-platform native-ish look
• Simple mental model — widgets, geometry managers, event loop
• Fast prototyping — a working window in 5 lines
It’s not the flashiest toolkit, but combined with modern approaches (ttk themes, web
wrappers, custom styling) it’s capable of shipping real-world desktop apps.

Installation Check
import tkinter as tk
print([Link]) # Should print 8.6 or higher
[Link]().mainloop() # Opens a blank window

If you’re on Linux and tkinter isn’t installed:


# Ubuntu/Debian
sudo apt install python3-tk
# Fedora/RHEL
sudo dnf install python3-tkinter

Project Structure Convention


my_app/
├── [Link] # Entry point
├── [Link] # Root App class
├── views/ # Individual screen/panel classes
│ ├── main_view.py
│ └── settings_view.py
├── models/ # Data / business logic
│ └── task_model.py
├── assets/ # Icons, images
│ └── [Link]
├── [Link]
└── [Link] # PyInstaller spec (later)

2. Core Concepts: Windows, Geometry, and Layout


The Root Window
import tkinter as tk

root = [Link]()
[Link]("My App")
[Link]("800x600") # WIDTHxHEIGHT
[Link]("800x600+100+50") # ...+X_OFFSET+Y_OFFSET
[Link](400, 300)
[Link](1920, 1080)
[Link](True, False) # (width_resizable, height_resizable)

# App icon (use .ico on Windows, .png on Linux/macOS)


[Link]("assets/[Link]") # Windows
# [Link](True, [Link](file="assets/[Link]")) #
Cross-platform

[Link]() # Starts the event loop — blocks until window closes

The Three Geometry Managers


You must use exactly ONE geometry manager per container. Never mix pack and grid in
the same parent.

pack — Simple linear layout


import tkinter as tk

root = [Link]()
btn1 = [Link](root, text="Top")
[Link](side=[Link], fill=tk.X, padx=10, pady=5)

btn2 = [Link](root, text="Left")


[Link](side=[Link], fill=tk.Y)

btn3 = [Link](root, text="Right")


[Link](side=[Link])

# fill options: tk.X, tk.Y, [Link], [Link]


# expand=True: widget takes extra space
label = [Link](root, text="Center", bg="lightblue")
[Link](fill=[Link], expand=True)

[Link]()

grid — Table layout (most powerful)


import tkinter as tk

root = [Link]()
[Link](1, weight=1) # Column 1 expands with window

[Link](root, text="Name:").grid(row=0, column=0, sticky="e", padx=5,


pady=5)
name_entry = [Link](root)
name_entry.grid(row=0, column=1, sticky="ew", padx=5, pady=5)

[Link](root, text="Email:").grid(row=1, column=0, sticky="e",


padx=5, pady=5)
email_entry = [Link](root)
email_entry.grid(row=1, column=1, sticky="ew", padx=5, pady=5)

# sticky: "n", "s", "e", "w", "ns", "ew", "nsew" (cardinal


directions / fill)
# columnspan / rowspan for merged cells
[Link](root, text="Submit").grid(row=2, column=0, columnspan=2,
pady=10)

[Link]()

place — Absolute/relative positioning


# Use sparingly — doesn't resize well
btn = [Link](root, text="Centered")
[Link](relx=0.5, rely=0.5, anchor="center") # 50% from top-left

Frame — The Layout Container


Frame is the key to complex layouts. Each frame can use its own geometry manager.
import tkinter as tk

root = [Link]()
[Link]("600x400")

# Sidebar
sidebar = [Link](root, bg="#2c3e50", width=150)
[Link](side=[Link], fill=tk.Y)
sidebar.pack_propagate(False) # Prevent frame from shrinking to
content

# Main content area


content = [Link](root, bg="white")
[Link](side=[Link], fill=[Link], expand=True)

# Widgets inside sidebar use sidebar as parent


[Link](sidebar, text="Menu", bg="#2c3e50", fg="white",
font=("Arial", 12, "bold")).pack(pady=20)
for item in ["Dashboard", "Tasks", "Settings"]:
[Link](sidebar, text=item, bg="#34495e", fg="white",
relief=[Link], pady=8).pack(fill=tk.X)

[Link](content, text="Welcome!", font=("Arial", 24)).pack(pady=50)

[Link]()

Toplevel — Child Windows


def open_window():
win = [Link](root)
[Link]("Settings")
[Link]("400x300")
win.grab_set() # Makes it modal (blocks parent)
[Link](root) # Keeps it on top of parent

btn = [Link](root, text="Open Settings", command=open_window)


[Link]()

3. Essential Widgets
Label
import tkinter as tk
from tkinter import ttk

root = [Link]()

# Basic label
lbl = [Link](root, text="Hello World",
font=("Helvetica", 16, "bold"),
fg="#333333", bg="white",
wraplength=200, # Word-wrap at 200px
justify=[Link])
[Link](padx=20, pady=10)

# With image
img = [Link](file="[Link]")
img_label = [Link](root, image=img, text="Icon + Text",
compound=[Link]) # Image left of text
img_label.pack()

# Dynamic update
[Link](text="Updated!")
lbl["text"] = "Also works"

[Link]()

Button
import tkinter as tk

root = [Link]()

def on_click():
print("Clicked!")

# Standard button
btn = [Link](root, text="Click Me",
command=on_click,
bg="#3498db", fg="white",
font=("Arial", 11),
padx=15, pady=8,
relief=[Link],
cursor="hand2", # Pointer cursor on hover
activebackground="#2980b9",
activeforeground="white")
[Link](pady=10)

# Disable / enable
[Link](state=[Link])
[Link](state=[Link])

# Toggle button pattern


is_active = [Link](value=False)

def toggle():
is_active.set(not is_active.get())
toggle_btn.config(
text="ON" if is_active.get() else "OFF",
bg="#2ecc71" if is_active.get() else "#e74c3c"
)

toggle_btn = [Link](root, text="OFF", command=toggle,


bg="#e74c3c", fg="white", width=6)
toggle_btn.pack()

[Link]()

Entry (Single-line Text Input)


import tkinter as tk

root = [Link]()

# Basic entry with StringVar


name_var = [Link]()
entry = [Link](root, textvariable=name_var,
font=("Arial", 12),
width=30,
relief=[Link],
bg="#f0f0f0")
[Link](padx=20, pady=10)
[Link](0, "Placeholder text") # Initial text
[Link]() # Auto-focus

# Password field
pwd = [Link](root, show="*", font=("Arial", 12))
[Link](padx=20, pady=5)

# Trace changes
def on_change(*args):
print(f"Value: {name_var.get()}")

name_var.trace_add("write", on_change)

# Read / clear
value = [Link]()
[Link](0, [Link])

[Link]()

Text (Multi-line)
import tkinter as tk

root = [Link]()

# Scrolled text area


frame = [Link](root)
[Link](fill=[Link], expand=True, padx=10, pady=10)
text = [Link](frame, font=("Courier", 11),
wrap=[Link], # [Link], [Link], [Link]
undo=True,
maxundo=50,
relief=[Link],
bg="#1e1e1e", fg="#d4d4d4",
insertbackground="white") # Cursor color
[Link](side=[Link], fill=[Link], expand=True)

scrollbar = [Link](frame, command=[Link])


[Link](side=[Link], fill=tk.Y)
[Link](yscrollcommand=[Link])

# Insert / get / delete


[Link]([Link], "Line 1\nLine 2\n")
[Link]("1.0", "Prepended\n") # "[Link]" indexing

content = [Link]("1.0", [Link]) # Get all text


[Link]("1.0", [Link]) # Clear all

# Tags for syntax highlighting / styling


text.tag_configure("keyword", foreground="#569cd6", font=("Courier",
11, "bold"))
[Link]([Link], "def ")
text.tag_add("keyword", "end-5c", "end-1c")

# Read-only mode
[Link](state=[Link])
# To write to disabled widget:
[Link](state=[Link])
[Link]([Link], "...")
[Link](state=[Link])

[Link]()

Checkbutton and Radiobutton


import tkinter as tk

root = [Link]()

# Checkbuttons
options = ["Python", "JavaScript", "Rust"]
vars_ = []
for opt in options:
var = [Link]()
vars_.append(var)
cb = [Link](root, text=opt, variable=var,
font=("Arial", 11),
command=lambda o=opt, v=var: print(f"{o}:
{[Link]()}"))
[Link](anchor="w", padx=20)

# Radiobuttons
lang_var = [Link](value="Python")
for lang in ["Python", "Go", "Rust"]:
rb = [Link](root, text=lang, variable=lang_var,
value=lang,
font=("Arial", 11),
command=lambda: print(lang_var.get()))
[Link](anchor="w", padx=20)

[Link]()

Scale (Slider) and Spinbox


import tkinter as tk
from tkinter import ttk

root = [Link]()

# Horizontal scale
volume = [Link](value=50)
scale = [Link](root, variable=volume,
from_=0, to=100,
orient=[Link],
length=300,
tickinterval=25,
label="Volume",
command=lambda v: print(f"Volume: {v}"))
[Link](pady=10)

# ttk Spinbox
spin_var = [Link](value=5)
spinbox = [Link](root, from_=1, to=100,
textvariable=spin_var,
width=10,
command=lambda: print(spin_var.get()))
[Link]()

# String spinbox
days = [Link](root, values=("Mon", "Tue", "Wed", "Thu", "Fri"),
width=8)
[Link]()

[Link]()
4. Advanced Widgets
Combobox (Dropdown)
import tkinter as tk
from tkinter import ttk

root = [Link]()

# Editable dropdown
selected = [Link]()
combo = [Link](root, textvariable=selected,
values=["Option A", "Option B", "Option C"],
state="readonly", # Prevent manual input;
remove for editable
width=25)
[Link]("Option A") # Default
[Link](padx=20, pady=10)

def on_select(event):
print(f"Selected: {[Link]()}")

[Link]("<<ComboboxSelected>>", on_select)

# Dynamically update values


combo["values"] = ["New 1", "New 2", "New 3"]

[Link]()

Listbox
import tkinter as tk

root = [Link]()

frame = [Link](root)
[Link](padx=10, pady=10)

listbox = [Link](frame,
selectmode=[Link], # SINGLE, BROWSE,
MULTIPLE, EXTENDED
font=("Arial", 11),
bg="#f5f5f5",
activestyle="dotbox",
height=8, width=30)
[Link](side=[Link])

scroll = [Link](frame, orient=[Link],


command=[Link])
[Link](side=[Link], fill=tk.Y)
[Link](yscrollcommand=[Link])

items = ["Apple", "Banana", "Cherry", "Date", "Fig", "Grape"]


for item in items:
[Link]([Link], item)

# Selection handling
def on_select(event):
selection = [Link]()
selected = [[Link](i) for i in selection]
print(f"Selected: {selected}")

[Link]("<<ListboxSelect>>", on_select)

# Add / remove
[Link]([Link], "Mango")
[Link](0) # Remove first item

# Styling individual items


[Link](1, bg="#ffe0b2", fg="#e65100")

[Link]()

Treeview — The Data Grid


[Link] serves as both a tree view and a full-featured data table.
import tkinter as tk
from tkinter import ttk

root = [Link]()
[Link]("700x400")

# --- Setup ---


columns = ("id", "name", "age", "city")
tree = [Link](root, columns=columns, show="headings",
selectmode="browse")

# Column headers
for col in columns:
[Link](col, text=[Link](),
command=lambda c=col: sort_column(tree, c, False))

# Column widths and alignment


[Link]("id", width=60, anchor="center")
[Link]("name", width=200, anchor="w")
[Link]("age", width=80, anchor="center")
[Link]("city", width=150, anchor="w")
# Scrollbars
vsb = [Link](root, orient=[Link], command=[Link])
hsb = [Link](root, orient=[Link], command=[Link])
[Link](yscrollcommand=[Link], xscrollcommand=[Link])

[Link](row=0, column=0, sticky="nsew")


[Link](row=0, column=1, sticky="ns")
[Link](row=1, column=0, sticky="ew")
[Link](0, weight=1)
[Link](0, weight=1)

# Insert data
data = [
(1, "Alice Johnson", 28, "New York"),
(2, "Bob Smith", 34, "London"),
(3, "Carol Williams", 22, "Bangalore"),
]

for i, row in enumerate(data):


tag = "even" if i % 2 == 0 else "odd"
[Link]("", [Link], values=row, tags=(tag,))

# Alternating row colors


tree.tag_configure("even", background="#f9f9f9")
tree.tag_configure("odd", background="#ffffff")

# --- Sorting ---


def sort_column(tv, col, reverse):
data = [([Link](k, col), k) for k in tv.get_children("")]
try:
[Link](key=lambda x: float(x[0]), reverse=reverse)
except ValueError:
[Link](reverse=reverse)
for i, (_, k) in enumerate(data):
[Link](k, "", i)
[Link](k, tags=("even" if i % 2 == 0 else "odd",))
[Link](col, command=lambda: sort_column(tv, col, not
reverse))

# --- CRUD operations ---


def get_selected():
sel = [Link]()
if sel:
return [Link](sel[0], "values")
return None

def delete_selected():
sel = [Link]()
if sel:
[Link](sel[0])

def edit_selected(new_values):
sel = [Link]()
if sel:
[Link](sel[0], values=new_values)

# --- Hierarchical (tree mode) ---


# Use show="tree headings" and parent IDs for nested rows:
# parent_id = [Link]("", [Link], text="Parent", values=(...))
# [Link](parent_id, [Link], text="Child", values=(...))

[Link]()

Notebook (Tabs)
import tkinter as tk
from tkinter import ttk

root = [Link]()

notebook = [Link](root)
[Link](fill=[Link], expand=True, padx=5, pady=5)

# Create tab frames


tab1 = [Link](notebook)
tab2 = [Link](notebook)
tab3 = [Link](notebook)

[Link](tab1, text=" General ")


[Link](tab2, text=" Advanced ")
[Link](tab3, text=" About ")

# Populate tabs
[Link](tab1, text="General Settings", font=("Arial",
14)).pack(pady=20)
[Link](tab2, text="Advanced Options", font=("Arial",
14)).pack(pady=20)

# Listen for tab change


def on_tab_change(event):
tab = [Link]([Link]())
print(f"Switched to tab {tab}")

[Link]("<<NotebookTabChanged>>", on_tab_change)

# Programmatic switch
[Link](1) # Switch to tab index 1

[Link]()
Canvas — Drawing and Custom Widgets
import tkinter as tk
import math

root = [Link]()

canvas = [Link](root, width=500, height=400, bg="white")


[Link]()

# Shapes
canvas.create_rectangle(50, 50, 200, 150, fill="#3498db",
outline="#2980b9", width=2)
canvas.create_oval(220, 50, 370, 150, fill="#e74c3c", outline="")
canvas.create_line(50, 200, 400, 200, fill="#2ecc71", width=3,
dash=(10, 5))
canvas.create_polygon(250, 220, 200, 320, 300, 320,
fill="#f39c12", outline="#e67e22")
canvas.create_text(250, 360, text="Canvas Demo",
font=("Arial", 16, "bold"), fill="#333")

# Move / update items


rect_id = canvas.create_rectangle(10, 10, 60, 60, fill="red")
[Link](rect_id, 50, 50)
[Link](rect_id, fill="blue")
[Link](rect_id, 100, 100, 160, 160)

# Image on canvas
img = [Link](file="[Link]")
canvas.create_image(400, 100, image=img, anchor="nw")

# Click detection
def on_canvas_click(event):
items = canvas.find_overlapping(event.x-2, event.y-2, event.x+2,
event.y+2)
if items:
[Link](items[0], fill="purple")

[Link]("<Button-1>", on_canvas_click)

# Animation loop
def animate():
[Link](rect_id, 2, 0)
x1, _, x2, _ = [Link](rect_id)
if x2 > 500:
[Link](rect_id, -50, 100, 10, 160)
[Link](16, animate) # ~60fps

animate()
[Link]()
PanedWindow — Resizable Panes
import tkinter as tk
from tkinter import ttk

root = [Link]()
[Link]("700x500")

paned = [Link](root, orient=[Link])


[Link](fill=[Link], expand=True)

left = [Link](paned, width=200, relief=[Link])


right = [Link](paned)

[Link](left, weight=1)
[Link](right, weight=3)

[Link](left, text="Sidebar").pack(pady=10)
[Link](right, text="Main Content").pack(pady=10)

[Link]()

ScrolledText (Convenience Widget)


from [Link] import ScrolledText
import tkinter as tk

root = [Link]()
st = ScrolledText(root, width=60, height=20, font=("Courier", 11))
[Link](padx=10, pady=10, fill=[Link], expand=True)
[Link]([Link], "This widget has a built-in scrollbar.")
[Link]()

5. Styling with ttk and Themes


ttk Themes
import tkinter as tk
from tkinter import ttk

root = [Link]()
style = [Link]()

# List available themes


print(style.theme_names())
# ('winnative', 'clam', 'alt', 'default', 'classic', 'vista',
'xpnative')
# macOS adds: 'aqua'
# Linux: 'clam', 'alt', 'default', 'classic'
style.theme_use("clam") # Best cross-platform base for custom
styling

Custom ttk Styling


style = [Link]()
style.theme_use("clam")

# Style a Button
[Link]("[Link]",
background="#3498db",
foreground="white",
font=("Arial", 11, "bold"),
padding=(12, 6),
relief="flat",
borderwidth=0)

[Link]("[Link]",
background=[("active", "#2980b9"), ("disabled", "#bdc3c7")],
foreground=[("disabled", "#7f8c8d")])

# Style a Frame
[Link]("[Link]",
background="white",
relief="flat",
borderwidth=1)

# Style Treeview
[Link]("Treeview",
background="#ffffff",
fieldbackground="#ffffff",
rowheight=28,
font=("Arial", 10))
[Link]("[Link]",
background="#2c3e50",
foreground="white",
font=("Arial", 10, "bold"),
relief="flat")
[Link]("Treeview",
background=[("selected", "#3498db")],
foreground=[("selected", "white")])

# Usage
btn = [Link](root, text="Save", style="[Link]")
[Link]()

Third-Party Themes: ttkthemes and ttkbootstrap


pip install ttkthemes ttkbootstrap
# ttkthemes
from ttkthemes import ThemedTk
root = ThemedTk(theme="arc") # "arc", "breeze", "equilux" (dark),
"yaru"

# ttkbootstrap — most feature-rich


import ttkbootstrap as ttk
from [Link] import *

root = [Link](themename="darkly") # "flatly", "litera", "cosmo",


"darkly", "superhero"
[Link](root, text="Primary", bootstyle=PRIMARY).pack(pady=5)
[Link](root, text="Danger", bootstyle=DANGER).pack(pady=5)
[Link](root, text="Outline", bootstyle=(SUCCESS,
OUTLINE)).pack(pady=5)
[Link]()

6. Event Handling & Bindings


Event Binding
import tkinter as tk

root = [Link]()

# Keyboard events
[Link]("<Return>", lambda e: print("Enter pressed"))
[Link]("<Escape>", lambda e: [Link]())
[Link]("<Control-s>", lambda e: save())
[Link]("<Control-z>", lambda e: undo())
[Link]("<F5>", lambda e: refresh())

# Key press anywhere in app (bind_all)


root.bind_all("<Control-q>", lambda e: [Link]())

# Mouse events
canvas = [Link](root, width=400, height=300, bg="white")
[Link]()
[Link]("<Button-1>", lambda e: print(f"Left click at {e.x},
{e.y}"))
[Link]("<Button-3>", lambda e: print("Right click"))
[Link]("<Double-Button-1>", lambda e: print("Double click"))
[Link]("<B1-Motion>", lambda e: print(f"Dragging to {e.x},
{e.y}"))
[Link]("<MouseWheel>", lambda e: print(f"Scroll delta:
{[Link]}"))

# Focus events
entry = [Link](root)
[Link]()
[Link]("<FocusIn>", lambda e: [Link](bg="#fffde7"))
[Link]("<FocusOut>", lambda e: [Link](bg="white"))

# Widget-level vs window-level
# [Link]() — only that widget
# [Link]() — root window only
# root.bind_all() — entire application

[Link]()

StringVar, IntVar, BooleanVar, DoubleVar


import tkinter as tk

root = [Link]()

# Variables auto-update linked widgets


name = [Link](value="")
count = [Link](value=0)
enabled = [Link](value=True)

entry = [Link](root, textvariable=name)


[Link]()

label = [Link](root, textvariable=name) # Mirror of entry


[Link]()

# Trace: run callback on change


def on_name_change(*args):
print(f"Name is now: {[Link]()}")

trace_id = name.trace_add("write", on_name_change) # "write", "read",


"unset"
name.trace_remove("write", trace_id) # Remove trace

# Programmatic set
[Link]("John Doe")
print([Link]())

[Link]()

after() — The Event Loop Timer


Never use [Link]() in a tkinter app — it freezes the GUI. Use after() instead.
import tkinter as tk
import datetime
root = [Link]()
time_label = [Link](root, font=("Arial", 24))
time_label.pack(pady=20)

def update_clock():
now = [Link]().strftime("%H:%M:%S")
time_label.config(text=now)
[Link](1000, update_clock) # Schedule next call in 1000ms

update_clock()
[Link]()

7. Dialogs, Menus, and System Tray


Built-in Dialogs
from tkinter import messagebox, filedialog, simpledialog, colorchooser
import tkinter as tk

root = [Link]()

# Message boxes
[Link]("Title", "Info message")
[Link]("Title", "Warning message")
[Link]("Title", "Error message")
result = [Link]("Confirm", "Are you sure?") # Returns
bool
result = [Link]("Confirm", "Proceed?")

# Simple input
name = [Link]("Input", "What is your name?",
parent=root)
age = [Link]("Input", "Your age:", minvalue=1,
maxvalue=120)

# File dialogs
path = [Link](
title="Open File",
initialdir="/home",
filetypes=[("Text files", "*.txt"), ("Python files", "*.py"),
("All files", "*.*")]
)
paths = [Link](...) # Multiple files
save_path = [Link](defaultextension=".txt")
folder = [Link](title="Select Folder")

# Color chooser
color = [Link](title="Pick a color",
initialcolor="#3498db")
# Returns: ((r, g, b), "#rrggbb") or (None, None) if cancelled
if color[1]:
print(f"Hex: {color[1]}")

Menu Bar
import tkinter as tk

root = [Link]()

menubar = [Link](root)
[Link](menu=menubar)

# File menu
file_menu = [Link](menubar, tearoff=0)
menubar.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="New", command=lambda: print("New"),
accelerator="Ctrl+N")
file_menu.add_command(label="Open...", command=lambda:
print("Open"), accelerator="Ctrl+O")
file_menu.add_separator()
file_menu.add_command(label="Exit", command=[Link])

# Edit menu
edit_menu = [Link](menubar, tearoff=0)
menubar.add_cascade(label="Edit", menu=edit_menu)
edit_menu.add_command(label="Undo", command=lambda: print("Undo"),
accelerator="Ctrl+Z")
edit_menu.add_command(label="Redo", command=lambda: print("Redo"),
accelerator="Ctrl+Y")

# View menu with checkbuttons


view_menu = [Link](menubar, tearoff=0)
menubar.add_cascade(label="View", menu=view_menu)
show_toolbar = [Link](value=True)
view_menu.add_checkbutton(label="Toolbar", variable=show_toolbar)

# Submenu
theme_menu = [Link](view_menu, tearoff=0)
view_menu.add_cascade(label="Theme", menu=theme_menu)
for t in ["Light", "Dark", "System"]:
theme_menu.add_command(label=t, command=lambda x=t: print(f"Theme:
{x}"))

[Link]()

Context Menu (Right-click)


import tkinter as tk
root = [Link]()
text = [Link](root, width=40, height=10)
[Link]()

context_menu = [Link](root, tearoff=0)


context_menu.add_command(label="Cut", command=lambda:
text.event_generate("<<Cut>>"))
context_menu.add_command(label="Copy", command=lambda:
text.event_generate("<<Copy>>"))
context_menu.add_command(label="Paste", command=lambda:
text.event_generate("<<Paste>>"))
context_menu.add_separator()
context_menu.add_command(label="Select All",
command=lambda: text.tag_add("sel", "1.0",
"end"))

def show_context(event):
context_menu.tk_popup(event.x_root, event.y_root)

[Link]("<Button-3>", show_context)
[Link]()

System Tray with pystray


pip install pystray Pillow

import tkinter as tk
import threading
from PIL import Image, ImageDraw
import pystray

class TrayApp:
def __init__(self):
[Link] = [Link]()
[Link]("Tray App")
[Link]("WM_DELETE_WINDOW", self.hide_window)

[Link]([Link], text="App is running. Close hides to


tray.").pack(pady=20)
[Link]([Link], text="Quit",
command=self.quit_app).pack()

self.tray_icon = self._create_tray_icon()
tray_thread = [Link](target=self.tray_icon.run,
daemon=True)
tray_thread.start()

def _create_icon_image(self):
img = [Link]("RGB", (64, 64), "#3498db")
d = [Link](img)
[Link]([16, 16, 48, 48], fill="white")
return img

def _create_tray_icon(self):
menu = [Link](
[Link]("Show", self.show_window, default=True),
[Link]("Quit", self.quit_app)
)
return [Link]("myapp", self._create_icon_image(), "My
App", menu)

def hide_window(self):
[Link]()

def show_window(self):
[Link](0, [Link])

def quit_app(self):
self.tray_icon.stop()
[Link](0, [Link])

def run(self):
[Link]()

TrayApp().run()

8. Multi-threading & Responsiveness


The Problem
Tkinter’s event loop is single-threaded. Running a long task on the main thread freezes the
UI completely. Never do I/O, sleep, or heavy computation directly in a callback.

Pattern 1: threading + queue (recommended)


import tkinter as tk
from tkinter import ttk
import threading
import queue
import time

class App([Link]):
def __init__(self):
super().__init__()
[Link]("Threading Demo")
[Link]("400x200")
[Link] = [Link]()

[Link] = [Link](self, text="Press Start",


font=("Arial", 12))
[Link](pady=20)

[Link] = [Link](self, mode="determinate",


length=300)
[Link]()

[Link] = [Link](self, text="Start Task",


command=self.start_task)
[Link](pady=10)

[Link](100, self.poll_queue)

def start_task(self):
[Link](state=[Link])
thread = [Link](target=self.background_task,
daemon=True)
[Link]()

def background_task(self):
"""Runs in background thread — never touch widgets here."""
for i in range(1, 11):
[Link](0.5) # Simulate
work
[Link](("progress", i * 10, f"Processing step
{i}/10"))
[Link](("done", 100, "Task complete!"))

def poll_queue(self):
"""Drains queue on main thread — safe to update widgets
here."""
try:
while True:
msg_type, value, text = [Link].get_nowait()
[Link]["value"] = value
[Link](text=text)
if msg_type == "done":
[Link](state=[Link])
except [Link]:
pass
[Link](100, self.poll_queue)

App().mainloop()

Pattern 2: [Link] + after()


import tkinter as tk
from tkinter import ttk
from [Link] import ThreadPoolExecutor
import requests # pip install requests
executor = ThreadPoolExecutor(max_workers=4)

class App([Link]):
def __init__(self):
super().__init__()
self.result_var = [Link](value="...")
[Link](self, textvariable=self.result_var).pack(pady=20)
[Link](self, text="Fetch Data", command=[Link]).pack()

def fetch(self):
self.result_var.set("Loading...")
future = [Link](self._do_request)
[Link](100, lambda: self._check_future(future))

def _do_request(self):
r = [Link]("[Link]
return [Link]().get("current_user_url", "N/A")

def _check_future(self, future):


if [Link]():
try:
result = [Link]()
self.result_var.set(f"Result: {result}")
except Exception as e:
self.result_var.set(f"Error: {e}")
else:
[Link](100, lambda: self._check_future(future))

App().mainloop()

Pattern 3: asyncio + tkinter


import tkinter as tk
from tkinter import ttk
import asyncio
import threading

class AsyncApp([Link]):
def __init__(self):
super().__init__()
[Link] = asyncio.new_event_loop()
self._start_async_loop()

[Link] = [Link](self, text="Ready", font=("Arial", 12))


[Link](pady=20)
[Link](self, text="Run Async",
command=self.run_async_task).pack()

def _start_async_loop(self):
def run():
asyncio.set_event_loop([Link])
[Link].run_forever()
[Link](target=run, daemon=True).start()

def run_async_task(self):
asyncio.run_coroutine_threadsafe(self._async_work(),
[Link])

async def _async_work(self):


[Link](0, lambda: [Link](text="Working..."))
await [Link](2) # Async I/O here (aiohttp, etc.)
[Link](0, lambda: [Link](text="Done!"))

AsyncApp().mainloop()

Thread Safety Rules


• Never call widget methods from a background thread. Only the main thread can
safely call .config(), .insert(), .delete(), etc.
• Use [Link](0, callback) to schedule UI updates from any thread.
• Use [Link] to pass data from background threads to the main thread.
• Use [Link] to signal cancellation to background threads.

9. Data Persistence: SQLite, JSON, Config Files


SQLite with tkinter
import sqlite3
import tkinter as tk
from tkinter import ttk

DB_PATH = "[Link]"

def init_db():
conn = [Link](DB_PATH)
[Link]("""
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
done INTEGER DEFAULT 0
)
""")
[Link]()
[Link]()

def get_tasks():
conn = [Link](DB_PATH)
rows = [Link]("SELECT id, title, done FROM
tasks").fetchall()
[Link]()
return rows

def add_task(title: str):


conn = [Link](DB_PATH)
[Link]("INSERT INTO tasks (title) VALUES (?)", (title,))
[Link]()
[Link]()

def delete_task(task_id: int):


conn = [Link](DB_PATH)
[Link]("DELETE FROM tasks WHERE id = ?", (task_id,))
[Link]()
[Link]()

init_db()

# Wire into a UI
root = [Link]()
tree = [Link](root, columns=("id", "title", "done"),
show="headings")
for c in ("id", "title", "done"):
[Link](c, text=[Link]())
[Link](fill=[Link], expand=True)

def refresh():
[Link](*tree.get_children())
for row in get_tasks():
[Link]("", [Link], values=row)

refresh()
[Link]()

Config with configparser


import configparser
import os

CONFIG_PATH = "[Link]"

def load_config():
cfg = [Link]()
if [Link](CONFIG_PATH):
[Link](CONFIG_PATH)
else:
cfg["appearance"] = {"theme": "light", "font_size": "12"}
cfg["window"] = {"width": "800", "height": "600"}
save_config(cfg)
return cfg
def save_config(cfg):
with open(CONFIG_PATH, "w") as f:
[Link](f)

config = load_config()
theme = [Link]("appearance", "theme", fallback="light")
[Link]("appearance", "theme", "dark")
save_config(config)

10. Modern HTML Apps with Web Wrappers


Option 1: tkinterweb — Render HTML/CSS Natively
pip install tkinterweb

import tkinter as tk
from tkinterweb import HtmlFrame

root = [Link]()
[Link]("HTML App")
[Link]("900x700")

frame = HtmlFrame(root, horizontal_scrollbar="auto")


[Link](fill=[Link], expand=True)

# Load from URL


frame.load_url("[Link]

# Load from HTML string


html = """
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial; background: #1e1e1e; color: #d4d4d4;
padding: 20px; }
h1 { color: #569cd6; }
.card { background: #252526; border-radius: 8px; padding: 16px;
margin: 10px 0; }
button { background: #0e639c; color: white; border: none; padding:
8px 16px;
border-radius: 4px; cursor: pointer; }
button:hover { background: #1177bb; }
</style>
</head>
<body>
<h1>Hello from HTML</h1>
<div class="card">
<p>This is rendered natively inside tkinter.</p>
<button onclick="[Link]('btn_clicked')">Click
Me</button>
</div>
</body>
</html>
"""
frame.load_html(html)

# Handle clicks from HTML → Python


def on_link_click(url):
if [Link]("pycommand://"):
command = url[len("pycommand://"):]
print(f"Python received: {command}")

frame.on_link_click = on_link_click

[Link]()

Option 2: pywebview — Full WebKit/Chromium Bridge


pip install pywebview

import webview
import threading
import json

class API:
"""Python methods callable from JavaScript via
[Link]"""

def get_data(self):
return {"message": "Hello from Python!", "version": "1.0"}

def save_file(self, content):


with open("[Link]", "w") as f:
[Link](content)
return {"success": True}

def show_dialog(self):
# Runs on separate thread — post to main thread if needed
result =
[Link][0].create_file_dialog(webview.OPEN_DIALOG)
return result[0] if result else None

api = API()

html = """
<!DOCTYPE html>
<html>
<head>
<title>pywebview App</title>
<style>
body { font-family: 'Segoe UI', sans-serif; padding: 30px;
background: #f5f5f5; }
button { padding: 10px 20px; background: #2196f3; color: white;
border: none; border-radius: 5px; cursor: pointer;
margin: 5px; }
#output { background: white; padding: 16px; border-radius: 8px;
margin-top: 20px; min-height: 60px; }
</style>
</head>
<body>
<h1>pywebview Demo</h1>
<button onclick="getData()">Get Data from Python</button>
<button onclick="openFile()">Open File</button>
<div id="output">Output appears here...</div>

<script>
async function getData() {
const result = await [Link].get_data();
[Link]('output').textContent =
[Link](result, null, 2);
}

async function openFile() {


const path = await [Link].show_dialog();
if (path) {
[Link]('output').textContent = 'Selected: ' +
path;
}
}

// Send message to Python


function sendToPython(data) {
[Link].save_file(data);
}
</script>
</body>
</html>
"""

# Create window
window = webview.create_window(
title="My App",
html=html,
js_api=api,
width=900,
height=700,
resizable=True,
frameless=False,
on_top=False
)

[Link](debug=False)

Option 3: Embedding a React/HTML App via CEF


For full Chromium embedding in a tkinter shell:
pip install cefpython3

from cefpython3 import cefpython as cef


import tkinter as tk
import sys, os

def main():
[Link] = [Link]
settings = {"windowless_rendering_enabled": False}
[Link](settings)

root = [Link]()
[Link]("CEF Browser")
[Link]("1000x700")

# Embed browser
browser_frame = [Link](root)
browser_frame.pack(fill=[Link], expand=True)
[Link]()

window_info = [Link]()
rect = [0, 0, browser_frame.winfo_width(),
browser_frame.winfo_height()]
window_info.SetAsChild(browser_frame.winfo_id(), rect)

browser = [Link](window_info,
url="[Link]

[Link]()
[Link]()

if __name__ == "__main__":
main()

Hybrid Pattern: tkinter Shell + Embedded Browser


For maximum capability, run a local server serving your React/Vue app:
import tkinter as tk
from tkinter import ttk
import webview
import threading
from [Link] import SimpleHTTPRequestHandler, HTTPServer
import os

PORT = 5173 # Or wherever your Vite/React dev/dist server runs

class LocalServer([Link]):
def __init__(self, directory):
super().__init__(daemon=True)
[Link] = directory

def run(self):
[Link]([Link])
server = HTTPServer(("localhost", PORT),
SimpleHTTPRequestHandler)
server.serve_forever()

# Start local server serving ./dist/


LocalServer("./dist").start()

# Open webview pointing at it


window = webview.create_window("React App", f"[Link]
{PORT}", width=1200, height=800)
[Link]()

11. Packaging & Distribution


Method 1: pip / PyPI (Library or Installable App)
Best for distributing Python packages or CLI+GUI tools to Python users.
[Link]:
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "[Link]:build"

[project]
name = "my-gui-app"
version = "1.0.0"
description = "A tkinter GUI application"
requires-python = ">=3.10"
dependencies = [
"ttkbootstrap>=1.10",
"requests>=2.31",
"Pillow>=10.0"
]

[[Link]]
my-app = "my_app.main:main" # Installs a CLI entry point

pip install build twine


python -m build # Creates dist/*.whl and dist/*.[Link]
twine upload dist/* # Upload to PyPI

Install with: pip install my-gui-app && my-app

Method 2: PyInstaller (Single Executable)


Most popular. Bundles Python + app into a standalone binary.
pip install pyinstaller

Basic usage:
# One-folder bundle
pyinstaller [Link] --name "MyApp" --icon assets/[Link]

# One-file executable (slower startup, good for distribution)


pyinstaller [Link] --name "MyApp" --onefile --icon assets/[Link]

# No terminal window (Windows)


pyinstaller [Link] --onefile --windowed --icon assets/[Link]

Custom .spec file (recommended for complex apps):


# [Link]
from [Link] import collect_data_files,
collect_submodules

a = Analysis(
["[Link]"],
pathex=["."],
binaries=[],
datas=[
("assets/", "assets/"), # Include assets folder
("[Link]", "."), # Include config file
],
hiddenimports=[
"tkinter",
"ttkbootstrap",
"PIL._tkinter_finder",
],
hookspath=[],
runtime_hooks=[],
excludes=["pytest", "unittest"],
noarchive=False,
)

pyz = PYZ([Link])
exe = EXE(
pyz, [Link], [Link], [Link], [Link],
name="MyApp",
debug=False,
console=False, # False = no terminal window
icon="assets/[Link]",
upx=True, # Compress (requires UPX installed)
)

pyinstaller [Link]

Common PyInstaller gotchas: - Hidden imports for dynamic importlib usage — add to
hiddenimports - Data files (images, CSS, templates) need to be listed in datas - Use
sys._MEIPASS to get the bundle path at runtime:
import sys, os

def resource_path(relative):
"""Get absolute path to resource — works for dev and
PyInstaller."""
base = getattr(sys, "_MEIPASS", [Link]("."))
return [Link](base, relative)

icon_path = resource_path("assets/[Link]")

Method 3: cx_Freeze
Good alternative to PyInstaller, especially on Linux.
pip install cx_Freeze

[Link]:
from cx_Freeze import setup, Executable
import sys

base = "Win32GUI" if [Link] == "win32" else None

build_exe_options = {
"packages": ["tkinter", "ttkbootstrap"],
"include_files": [("assets/", "assets/")],
"excludes": ["unittest", "pytest"],
}

setup(
name="MyApp",
version="1.0",
description="My tkinter App",
options={"build_exe": build_exe_options},
executables=[
Executable(
"[Link]",
base=base,
target_name="MyApp",
icon="assets/[Link]",
)
]
)

python [Link] build # Outputs to build/exe.*


python [Link] bdist_msi # Creates Windows installer

Method 4: Nuitka (Compiled Binary)


Nuitka compiles Python to C, then to a native binary. Slower build, faster startup, harder to
reverse-engineer.
pip install nuitka
# Windows: also needs VS Build Tools
# Linux: needs gcc

# Standalone build (no Python required on target)


python -m nuitka \
--standalone \
--onefile \
--windows-disable-console \
--windows-icon-from-ico=assets/[Link] \
--include-data-dir=assets=assets \
--enable-plugin=tk-inter \
--output-dir=dist \
[Link]

Key Nuitka flags:

Flag
--standalone
--onefile
--enable-plugin=tk-inter
--enable-plugin=numpy
--include-data-dir=src=dst
--windows-disable-console
--lto=yes
Method 5: briefcase (cross-platform, app store ready)
pip install briefcase
briefcase new # Interactive project setup
briefcase dev # Run in dev mode
briefcase build # Build for current platform
briefcase package # Create installer/dmg/deb
briefcase run # Run the built app

Comparison Table
Method Platforms Output Startup Skill Level Best For
pip/PyPI All Package n/a Low Dev tools,
libraries
PyInstalle Win/Mac/ Folder Medium Low Most apps
r Linux or .exe
cx_Freeze Win/Mac/ Folder Medium Medium Windows
Linux installers
Nuitka Win/Mac/ Native Fast Medium-High Performance
Linux binary , obfuscation
briefcase Win/Mac/ Native Medium Medium App store
Linux/iOS/ app distribution
Android

12. Real-World Project: A Full Task Manager App


This brings together everything: grid layout, treeview, dialogs, threading, SQLite, and ttk
styling.
# task_manager.py — Complete working example
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import sqlite3
import threading
import queue
import time
import os

DB_PATH = "[Link]"

# ─────────────────────────── Database Layer


───────────────────────────

def init_db():
with [Link](DB_PATH) as conn:
[Link]("""
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
prio TEXT DEFAULT 'Medium',
done INTEGER DEFAULT 0,
ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")

def db_get_all():
with [Link](DB_PATH) as conn:
return [Link](
"SELECT id, title, prio, done FROM tasks ORDER BY ts DESC"
).fetchall()

def db_add(title, prio):


with [Link](DB_PATH) as conn:
[Link]("INSERT INTO tasks (title, prio) VALUES (?, ?)",
(title, prio))

def db_delete(task_id):
with [Link](DB_PATH) as conn:
[Link]("DELETE FROM tasks WHERE id = ?", (task_id,))

def db_toggle(task_id):
with [Link](DB_PATH) as conn:
[Link]("UPDATE tasks SET done = 1 - done WHERE id = ?",
(task_id,))

# ─────────────────────────── Main App ───────────────────────────

class TaskManagerApp([Link]):
def __init__(self):
super().__init__()
[Link]("Task Manager")
[Link]("750x520")
[Link](500, 350)

self._setup_style()
self._build_ui()
self._queue = [Link]()

init_db()
self.refresh_list()
[Link](200, self._poll_queue)

def _setup_style(self):
style = [Link](self)
style.theme_use("clam")
BG = "#f0f2f5"
ACCENT = "#4f46e5"
[Link](bg=BG)

[Link]("TFrame", background=BG)
[Link]("TLabel", background=BG, font=("Segoe UI",
10))
[Link]("[Link]", font=("Segoe UI", 18, "bold"),
foreground=ACCENT, background=BG)
[Link]("[Link]",
background=ACCENT, foreground="white",
font=("Segoe UI", 10, "bold"),
padding=(10, 6), relief="flat", borderwidth=0)
[Link]("[Link]",
background=[("active", "#4338ca"), ("disabled",
"#a5b4fc")])
[Link]("[Link]",
background="#ef4444", foreground="white",
font=("Segoe UI", 10), padding=(10, 6),
relief="flat")
[Link]("[Link]", background=[("active",
"#dc2626")])
[Link]("Treeview", rowheight=32, font=("Segoe UI",
10),
background="white", fieldbackground="white")
[Link]("[Link]", font=("Segoe UI", 10,
"bold"),
background="#e0e7ff", foreground="#312e81")
[Link]("Treeview", background=[("selected", ACCENT)],
foreground=[("selected", "white")])

def _build_ui(self):
# ── Header
header = [Link](self, padding=(20, 15, 20, 10))
[Link](fill=tk.X)
[Link](header, text="Task Manager",
style="[Link]").pack(side=[Link])
self.count_label = [Link](header, text="", font=("Segoe
UI", 9),
foreground="#6b7280")
self.count_label.pack(side=[Link], padx=12)

# ── Add Task Bar


add_bar = [Link](self, padding=(20, 0, 20, 10))
add_bar.pack(fill=tk.X)
add_bar.columnconfigure(0, weight=1)

self.task_var = [Link]()
entry = [Link](add_bar, textvariable=self.task_var,
font=("Segoe UI", 11))
[Link](row=0, column=0, sticky="ew", ipady=5, padx=(0, 8))
[Link]("<Return>", lambda e: self.add_task())

self.prio_var = [Link](value="Medium")
prio_combo = [Link](add_bar, textvariable=self.prio_var,
values=["High", "Medium", "Low"],
state="readonly", width=9)
prio_combo.grid(row=0, column=1, padx=(0, 8))

[Link](add_bar, text="+ Add", style="[Link]",


command=self.add_task).grid(row=0, column=2)

# ── Treeview
tree_frame = [Link](self, padding=(20, 0, 20, 10))
tree_frame.pack(fill=[Link], expand=True)
tree_frame.columnconfigure(0, weight=1)
tree_frame.rowconfigure(0, weight=1)

cols = ("id", "title", "priority", "status")


[Link] = [Link](tree_frame, columns=cols,
show="headings",
selectmode="browse")
[Link]("id", text="#",
anchor="center")
[Link]("title", text="Task")
[Link]("priority", text="Priority",
anchor="center")
[Link]("status", text="Status",
anchor="center")
[Link]("id", width=45, anchor="center")
[Link]("title", width=350)
[Link]("priority", width=90, anchor="center")
[Link]("status", width=90, anchor="center")

vsb = [Link](tree_frame, orient=[Link],


command=[Link])
[Link](yscrollcommand=[Link])
[Link](row=0, column=0, sticky="nsew")
[Link](row=0, column=1, sticky="ns")

[Link].tag_configure("done", foreground="#9ca3af")
[Link].tag_configure("high", foreground="#dc2626")
[Link].tag_configure("medium", foreground="#d97706")
[Link].tag_configure("low", foreground="#16a34a")
[Link]("<Double-1>", lambda e: self.toggle_task())

# ── Action Buttons
btn_bar = [Link](self, padding=(20, 0, 20, 15))
btn_bar.pack(fill=tk.X)
[Link](btn_bar, text="✓ Toggle Done",
style="[Link]",
command=self.toggle_task).pack(side=[Link])
[Link](btn_bar, text="✕ Delete",
style="[Link]",
command=self.delete_task).pack(side=[Link], padx=8)
[Link](btn_bar, text="⟳ Refresh",
command=self.refresh_list).pack(side=[Link])

# ── Status Bar
self.status_var = [Link](value="Ready")
status_bar = [Link](self, textvariable=self.status_var,
relief=[Link], anchor="w",
font=("Segoe UI", 9),
foreground="#6b7280")
status_bar.pack(fill=tk.X, side=[Link])

def refresh_list(self):
"""Load tasks from DB in background thread."""
self.status_var.set("Loading...")
[Link](target=self._load_tasks, daemon=True).start()

def _load_tasks(self):
rows = db_get_all()
self._queue.put(("refresh", rows))

def _render_tasks(self, rows):


[Link](*[Link].get_children())
for row in rows:
task_id, title, prio, done = row
status = "✓ Done" if done else "Pending"
tags = []
if done:
[Link]("done")
else:
[Link]([Link]())
[Link]("", [Link], iid=str(task_id),
values=(task_id, title, prio, status),
tags=tuple(tags))
total = len(rows)
done = sum(1 for r in rows if r[3])
self.count_label.config(text=f"({done}/{total} done)")
self.status_var.set(f"Loaded {total} tasks")

def add_task(self):
title = self.task_var.get().strip()
if not title:
[Link]("Input Error", "Please enter a task
title.")
return
prio = self.prio_var.get()
self.task_var.set("")
[Link](target=self._add_task_bg, args=(title, prio),
daemon=True).start()

def _add_task_bg(self, title, prio):


db_add(title, prio)
rows = db_get_all()
self._queue.put(("refresh", rows))
self._queue.put(("status", f'Added task: "{title}"'))

def toggle_task(self):
sel = [Link]()
if not sel:
return
task_id = int(sel[0])
[Link](target=self._toggle_bg, args=(task_id,),
daemon=True).start()

def _toggle_bg(self, task_id):


db_toggle(task_id)
rows = db_get_all()
self._queue.put(("refresh", rows))

def delete_task(self):
sel = [Link]()
if not sel:
return
task_id = int(sel[0])
title = [Link](sel[0], "values")[1]
if [Link]("Confirm", f'Delete task "{title}"?'):
[Link](target=self._delete_bg, args=(task_id,),
daemon=True).start()

def _delete_bg(self, task_id):


db_delete(task_id)
rows = db_get_all()
self._queue.put(("refresh", rows))
self._queue.put(("status", "Task deleted"))

def _poll_queue(self):
try:
while True:
msg = self._queue.get_nowait()
if msg[0] == "refresh":
self._render_tasks(msg[1])
elif msg[0] == "status":
self.status_var.set(msg[1])
except [Link]:
pass
[Link](100, self._poll_queue)

if __name__ == "__main__":
app = TaskManagerApp()
[Link]()

Quick Reference Cheat Sheet


┌──────────────────────────────────────────────────────────┐
│ GEOMETRY MANAGERS │
│ pack() — stack vertically/horizontally, simple layouts │
│ grid() — table layout, use for forms & complex UIs │
│ place() — absolute, avoid unless you know the size │
│ │
│ LAYOUT TIPS │
│ columnconfigure(n, weight=1) → column expands │
│ rowconfigure(n, weight=1) → row expands │
│ sticky="nsew" → fill cell │
│ pack_propagate(False) → fix frame size │
│ │
│ THREAD SAFETY │
│ ✓ [Link](0, lambda: [Link](...)) │
│ ✓ [Link] to pass data to main thread │
│ ✗ Never call widget methods from background threads │
│ │
│ PACKAGING QUICK PICK │
│ Quick dist to Python users → pip / PyPI │
│ Windows/Mac/Linux .exe → PyInstaller --onefile │
│ Compiled native binary → Nuitka │
│ Windows installer → cx_Freeze bdist_msi │
│ App store ready → briefcase │
└──────────────────────────────────────────────────────────┘

You might also like