Tkinter comes with Python, needs no installation, and is still the quickest way to put a window on the screen. This is a short tour written for Python 3 and the themed widgets.
import tkinter as tk
root = tk.Tk()
tk.Label(root, text="Hello").pack(padx=40, pady=20)
root.mainloop()
Three things are happening. Tk() makes the main window. A widget is created with its parent as the first argument. mainloop() hands control to Tk, which waits for events and only returns when the window closes.
In Python 2 the module was called Tkinter with a capital T. In Python 3 it is tkinter. That single rename accounts for most of the broken examples you will find online.
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.title("Example")
ttk.Label(root, text="Name").grid(row=0, column=0, padx=8, pady=8)
ttk.Entry(root).grid(row=0, column=1, padx=8, pady=8)
ttk.Button(root, text="Go").grid(row=1, column=1, sticky="e", padx=8, pady=8)
root.mainloop()
The ttk widgets follow the look of the operating system, while the plain tk ones keep the older motif appearance. Use ttk for anything anyone else will see. A few widgets, such as Text, Canvas and Listbox, exist only in plain tk, and mixing the two is normal.
A widget appears only once you give it to a geometry manager. There are three, and they do not mix inside the same container.
| Manager | Good for | The idea |
|---|---|---|
pack | Simple stacks and toolbars | Each widget is pushed against a side of the space left over |
grid | Forms and most real layouts | Rows and columns, like a table |
place | Rare, fixed positions | Exact coordinates, which do not adapt to font size or resizing |
Use grid unless the layout is genuinely a stack. To make it stretch when the window is resized, tell the container which row and column should take the extra space:
root.columnconfigure(1, weight=1)
root.rowconfigure(0, weight=1)
Tk keeps widget values in its own variable objects, which is the part of the model people find odd at first.
name = tk.StringVar(value="world")
ttk.Entry(root, textvariable=name).grid(row=0, column=0)
def greet():
print("hello", name.get())
ttk.Button(root, text="Greet", command=greet).grid(row=0, column=1)
There is a variable class for each kind: StringVar, IntVar, DoubleVar and BooleanVar. They can be read with get(), written with set(), and watched with trace_add.
def clicked(event):
print(event.x, event.y)
canvas = tk.Canvas(root, width=300, height=200, background="white")
canvas.grid(row=0, column=0)
canvas.bind("<Button-1>", clicked)
root.bind("<Escape>", lambda e: root.destroy())
Buttons take a command, everything else uses bind with an event pattern. The handler receives an event object with the position, the key and the widget involved.
Any long piece of work freezes the window, because Tk cannot process events while your function runs. For anything slow, either do the work in a thread and post results back with after, or break it into pieces and schedule them:
def tick(n=0):
label.config(text=str(n))
root.after(1000, tick, n + 1)
tick()
after is also the only safe way to touch widgets from another thread. Tk is not thread safe, so background threads should hand work back to the main loop rather than updating widgets directly.
Tk on its own reads GIF and PNG. For JPEG and everything else, use Pillow's ImageTk:
import tkinter as tk
from PIL import Image, ImageTk
root = tk.Tk()
im = Image.open("photo.jpg")
im.thumbnail((400, 400), Image.Resampling.LANCZOS)
photo = ImageTk.PhotoImage(im)
label = tk.Label(root, image=photo)
label.image = photo
label.pack()
root.mainloop()
label.image = photo line is not redundant. Tk does not hold a reference to the image, so without it Python garbage collects the photo and you get an empty box. This catches everybody at least once.from tkinter import filedialog, messagebox
path = filedialog.askopenfilename(filetypes=[("Images", "*.png *.jpg")])
if path:
messagebox.showinfo("Chosen", path)
Tkinter is a wrapper around Tk, so the Tcl documentation still answers questions that Python documentation does not, particularly about widget options. The Python standard library reference covers the module itself, and for imaging work the Pillow pages here pick up where this leaves off.