Free Ebook cover Desktop Apps with Tkinter: A Beginner’s Guide to Python GUIs

Desktop Apps with Tkinter: A Beginner’s Guide to Python GUIs

New course

10 pages

Core Tkinter Widgets: Labels, Buttons, Entries, Text, and Variables

Capítulo 4

Estimated reading time: 9 minutes

+ Exercise

Goal: Capture and Display User Input

In this chapter you will build a small interface that collects user input, lets the user choose options, and then displays the result. You will use core widgets: Label, Entry, Text, Button, Checkbutton, and Radiobutton. You will also use Tkinter variables (StringVar, IntVar, BooleanVar) to bind widget state to Python values (two-way binding).

Core Widgets and the Options You Will Use

Label: Display text (and later, output)

A Label is for displaying non-editable text. It is often used for field names (like “Name:”) and for showing output. A label’s text can be set directly with the text option, or dynamically via a StringVar using the textvariable option.

  • text: static text shown by the label
  • textvariable: bind the label to a StringVar for dynamic updates
  • width: a fixed width in text units (useful for alignment)

Entry: Single-line input

An Entry is a single-line text input. You can read its value with entry.get() or bind it to a StringVar using the textvariable option for two-way binding.

  • width: visible character width
  • state: "normal" (editable), "disabled" (not editable), "readonly" (not editable but looks like a field; supported by ttk.Entry, not classic tk.Entry)

Text: Multi-line input/output

A Text widget is for multi-line text. Unlike Entry, it does not bind directly to StringVar. You typically use text_widget.get("1.0", "end-1c") to read and text_widget.insert(...) or text_widget.delete(...) to update.

  • width: character width
  • state: "normal" or "disabled" (useful for read-only output areas)

Button: Trigger actions with command

A Button triggers a Python function when clicked. The function is provided via the command option. The function should be passed without parentheses (you pass the function object, not the result of calling it).

Continue in our app.

You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.

Or continue reading below...
Download App

Download the app

  • text: label on the button
  • command: function to run on click
  • state: enable/disable the button
  • width: optional fixed width

Checkbutton: On/off choice

A Checkbutton represents a boolean choice. It is commonly bound to a BooleanVar (or sometimes an IntVar with 0/1). When the user toggles it, the variable updates automatically; when you set the variable in code, the checkbutton updates automatically.

  • text: label next to the checkbox
  • variable: a BooleanVar/IntVar to bind to
  • command: optional function called when toggled

Radiobutton: Choose one option from many

Radiobutton widgets work in a group: they share the same variable (usually an IntVar or StringVar), and each radiobutton sets that variable to its own value. This is ideal for “pick one” choices.

  • text: label for the option
  • variable: shared StringVar/IntVar
  • value: the value assigned when selected
  • command: optional function called when selection changes

Tkinter Variables: Two-Way Binding

Tkinter variables are special objects that keep a value and notify widgets when it changes. They are the simplest way to keep UI state and Python state synchronized.

  • StringVar: stores text
  • IntVar: stores integers (often used for radiobutton groups)
  • BooleanVar: stores True/False (often used for checkbuttons)

Key methods:

  • var.get(): read the current value
  • var.set(value): update the value (and the UI updates automatically if bound)

Step-by-Step Build: A Mini Data Capture Interface

You will build a small form that collects a name, an email, a subscription checkbox, a contact preference (radio buttons), and a multi-line note. When the user clicks “Submit”, you will display a formatted summary. You will also add a “Clear” button that resets everything and clears the output.

Step 1: Create variables for binding

We will bind Entry widgets to StringVar, the Checkbutton to a BooleanVar, and the Radiobutton group to a StringVar. We will also use a StringVar for output so we can show results in a label.

Step 2: Create widgets and connect them to variables

Notice how textvariable and variable connect widgets to Tkinter variables. For the multi-line Text widget, we will read and clear it using get/delete because it does not support StringVar directly.

Step 3: Implement submit and clear actions

The “Submit” button reads values from variables (and the Text widget), formats a summary, and writes it to the output variable. The “Clear” button resets variables with .set(...), clears the Text widget, and clears the output.

import tkinter as tk

root = tk.Tk()
root.title("Input Demo")

# --- Tkinter variables (two-way binding) ---
name_var = tk.StringVar(value="")
email_var = tk.StringVar(value="")
subscribe_var = tk.BooleanVar(value=False)
contact_var = tk.StringVar(value="email")  # radio group
output_var = tk.StringVar(value="")

# --- Widgets ---
tk.Label(root, text="Name:").grid(row=0, column=0, sticky="w", padx=8, pady=6)
name_entry = tk.Entry(root, textvariable=name_var, width=30)
name_entry.grid(row=0, column=1, sticky="w", padx=8, pady=6)

tk.Label(root, text="Email:").grid(row=1, column=0, sticky="w", padx=8, pady=6)
email_entry = tk.Entry(root, textvariable=email_var, width=30)
email_entry.grid(row=1, column=1, sticky="w", padx=8, pady=6)

subscribe_cb = tk.Checkbutton(root, text="Subscribe to updates", variable=subscribe_var)
subscribe_cb.grid(row=2, column=1, sticky="w", padx=8, pady=6)

tk.Label(root, text="Preferred contact:").grid(row=3, column=0, sticky="w", padx=8, pady=6)
rb_email = tk.Radiobutton(root, text="Email", variable=contact_var, value="email")
rb_phone = tk.Radiobutton(root, text="Phone", variable=contact_var, value="phone")
rb_email.grid(row=3, column=1, sticky="w", padx=8, pady=2)
rb_phone.grid(row=4, column=1, sticky="w", padx=8, pady=2)

tk.Label(root, text="Notes:").grid(row=5, column=0, sticky="nw", padx=8, pady=6)
notes_text = tk.Text(root, width=40, height=6)
notes_text.grid(row=5, column=1, sticky="w", padx=8, pady=6)

# Output shown in a label (read-only by nature)
tk.Label(root, text="Output:").grid(row=6, column=0, sticky="nw", padx=8, pady=6)
output_label = tk.Label(root, textvariable=output_var, width=50, anchor="w", justify="left")
output_label.grid(row=6, column=1, sticky="w", padx=8, pady=6)

# --- Actions ---
def submit():
    name = name_var.get().strip()
    email = email_var.get().strip()
    subscribed = subscribe_var.get()
    contact = contact_var.get()
    notes = notes_text.get("1.0", "end-1c").strip()

    summary = (
        f"Name: {name or '(missing)'}\n"
        f"Email: {email or '(missing)'}\n"
        f"Subscribed: {'Yes' if subscribed else 'No'}\n"
        f"Contact via: {contact}\n"
        f"Notes: {notes or '(none)'}"
    )
    output_var.set(summary)


def clear():
    name_var.set("")
    email_var.set("")
    subscribe_var.set(False)
    contact_var.set("email")
    notes_text.delete("1.0", "end")
    output_var.set("")


submit_btn = tk.Button(root, text="Submit", width=12, command=submit)
clear_btn = tk.Button(root, text="Clear", width=12, command=clear)
submit_btn.grid(row=7, column=1, sticky="w", padx=8, pady=10)
clear_btn.grid(row=7, column=1, sticky="w", padx=110, pady=10)

# Optional: put cursor in first field
name_entry.focus_set()

root.mainloop()

Reading and Updating Values: Common Patterns

Pattern 1: Read from variables (Entry/Checkbutton/Radiobutton)

When a widget is bound to a Tkinter variable, you typically read the value from the variable, not from the widget.

current_name = name_var.get()
is_subscribed = subscribe_var.get()
contact_choice = contact_var.get()

Pattern 2: Update the UI by setting variables

Setting the variable updates all widgets bound to it. This is useful for resets, defaults, and programmatic changes.

name_var.set("Ada Lovelace")
subscribe_var.set(True)
contact_var.set("phone")

Pattern 3: Work with Text widget content

The Text widget uses index strings. "1.0" means line 1, character 0. "end" includes a trailing newline, so "end-1c" is commonly used when reading.

notes = notes_text.get("1.0", "end-1c")
notes_text.delete("1.0", "end")
notes_text.insert("1.0", "Prefilled notes...")

Pattern 4: Make an output area read-only

A Label is naturally read-only, so it is a simple way to display output. If you want multi-line output with selectable text, you can use a Text widget and disable it after writing.

# Example: write to a Text output and lock it
output_text = tk.Text(root, width=50, height=6)
output_text.insert("1.0", "Hello")
output_text.config(state="disabled")

Exercises (Structured)

Exercise 1: Create a data-entry form

Build a form that collects the following:

  • First name (Entry bound to StringVar)
  • Last name (Entry bound to StringVar)
  • Age (Entry bound to StringVar or IntVar; keep it simple and validate later)
  • Newsletter opt-in (Checkbutton bound to BooleanVar)
  • Account type (Radiobutton group bound to StringVar, values: "free", "pro", "team")

Add a “Preview” button that writes a formatted summary to an output Label using textvariable.

Exercise 2: Add a clear/reset button

Add a “Reset” button that:

  • Sets all StringVar values to ""
  • Sets the newsletter BooleanVar to False
  • Sets the account type variable back to a default (for example "free")
  • Clears the output label by setting its StringVar to ""

Make sure you reset by calling .set(...) on variables rather than manually editing widget text.

Exercise 3: Show output in a read-only field

Replace the output label with a read-only output area:

  • Option A (simple): keep the Label and ensure it can display multiple lines (use justify and a reasonable width).
  • Option B (selectable output): use a Text widget for output, write the summary into it, then set state="disabled". When clearing, temporarily set state="normal", delete the content, then disable again.
# Output Text approach (snippet)

def set_output(text):
    output_text.config(state="normal")
    output_text.delete("1.0", "end")
    output_text.insert("1.0", text)
    output_text.config(state="disabled")

Widget Options in Practice: text, state, width, command

text

Used by Label, Button, Checkbutton, and Radiobutton to show a caption. For dynamic text, prefer textvariable on labels.

state

Use state to prevent interaction. Common values are "normal" and "disabled". Disabling is useful when you want to lock output or prevent submission until required fields are filled.

submit_btn.config(state="disabled")
submit_btn.config(state="normal")

width

width sets a widget’s size in text units (characters). It helps keep forms aligned and readable.

command

command connects a button (and optionally check/radio widgets) to behavior. Keep the callback focused: read state, compute result, update output.

def on_toggle():
    output_var.set(f"Subscribed: {subscribe_var.get()}")

subscribe_cb.config(command=on_toggle)

Now answer the exercise about the content:

In a Tkinter form, what is the recommended way to reset inputs and update the UI when widgets are bound to Tkinter variables?

You are right! Congratulations, now go to the next page

You missed! Try again.

With two-way binding, calling .set(...) on StringVar/BooleanVar/IntVar updates all bound widgets automatically. The Text widget doesn’t bind to variables, so you clear it with delete(...).

Next chapter

Event Handling in Tkinter: Commands, bind(), and Keyboard/Mouse Events

Arrow Right Icon
Download the app to earn free Certification and listen to the courses in the background, even with the screen off.