Previous | Tutorial index | Next
Building a real GUI application is not just about knowing individual widgets—it’s about combining them to solve a specific problem. The Temperature Converter is a classic "starter" project because it involves:
Entry widget)This tutorial will guide you through the full lifecycle of building this application:
By the end, you will have not only a working converter but also a process you can apply to any future GUI project.
As a user, I want to:
25.5).Before writing any code, sketch the GUI on paper (or using a tool). This helps you visualise the arrangement of widgets and plan the geometry manager.
Proposed layout (grid):
+-------------------------------------------------+
| Enter Temperature: [ ] | <- Row 0
| [To Fahrenheit] [To Celsius] | <- Row 1
| +-------------------------------------------+ | <- Row 2
| | Result will appear here | |
| +-------------------------------------------+ |
+-------------------------------------------------+
columnspan=2), with a sunken border to look like a display.This layout uses grid() with consistent padding for a clean look.
| Widget | Purpose | Options |
|---|---|---|
Label ("Enter Temperature:") |
Instruction text. | text="Enter Temperature:" |
Entry |
User input field. | Default width (10‑15 characters). |
Button (×2) |
Trigger conversion. | text="To Fahrenheit" / "To Celsius", command callback. |
Label (result) |
Display output or error. | text="Result will appear here", relief="sunken", width=40 (to ensure consistent size), anchor="center" (optional). |
Design choices:
padx, pady) is used for visual spacing.import tkinter as tk
root = tk.Tk()
root.title("Temperature Converter")
root.geometry("400x150") # Optional: set initial size
root.resizable(False, False) # Make it non‑resizable to keep layout simple (or leave resizable)
We write the logic before connecting it to the GUI. This keeps business logic separate from presentation.
def to_fahrenheit():
try:
celsius = float(entry.get())
fahrenheit = celsius * 9/5 + 32
result_label.config(text=f"{celsius:.2f}°C = {fahrenheit:.2f}°F")
except ValueError:
result_label.config(text="Invalid input – please enter a number.")
def to_celsius():
try:
fahrenheit = float(entry.get())
celsius = (fahrenheit - 32) * 5/9
result_label.config(text=f"{fahrenheit:.2f}°F = {celsius:.2f}°C")
except ValueError:
result_label.config(text="Invalid input – please enter a number.")
Error handling details:
float(entry.get()) may raise ValueError if the text is not numeric.sys.exit() or crash—the GUI remains usable.We create widgets and place them using grid().
# Row 0: Label and Entry
label = tk.Label(root, text="Enter Temperature:")
label.grid(row=0, column=0, padx=5, pady=5)
entry = tk.Entry(root, width=12)
entry.grid(row=0, column=1, padx=5, pady=5)
# Row 1: Two Buttons
btn_f = tk.Button(root, text="To Fahrenheit", command=to_fahrenheit)
btn_f.grid(row=1, column=0, padx=5, pady=5)
btn_c = tk.Button(root, text="To Celsius", command=to_celsius)
btn_c.grid(row=1, column=1, padx=5, pady=5)
# Row 2: Result Label (spanning two columns)
result_label = tk.Label(root, text="Result will appear here",
relief="sunken", width=40, anchor="center")
result_label.grid(row=2, column=0, columnspan=2, padx=5, pady=10)
Note: We assign the entry and result_label variables so the functions can access them.
root.mainloop()
import tkinter as tk
# --- Conversion functions ---
def to_fahrenheit():
"""Convert Celsius (from entry) to Fahrenheit and display."""
try:
celsius = float(entry.get())
fahrenheit = celsius * 9/5 + 32
result_label.config(text=f"{celsius:.2f}°C = {fahrenheit:.2f}°F")
except ValueError:
result_label.config(text="Invalid input – please enter a number.")
def to_celsius():
"""Convert Fahrenheit (from entry) to Celsius and display."""
try:
fahrenheit = float(entry.get())
celsius = (fahrenheit - 32) * 5/9
result_label.config(text=f"{fahrenheit:.2f}°F = {celsius:.2f}°C")
except ValueError:
result_label.config(text="Invalid input – please enter a number.")
# --- Create the main window ---
root = tk.Tk()
root.title("Temperature Converter")
root.geometry("400x150")
root.resizable(False, False)
# --- Create widgets ---
# Row 0
label = tk.Label(root, text="Enter Temperature:")
label.grid(row=0, column=0, padx=5, pady=5)
entry = tk.Entry(root, width=12)
entry.grid(row=0, column=1, padx=5, pady=5)
# Row 1
btn_f = tk.Button(root, text="To Fahrenheit", command=to_fahrenheit)
btn_f.grid(row=1, column=0, padx=5, pady=5)
btn_c = tk.Button(root, text="To Celsius", command=to_celsius)
btn_c.grid(row=1, column=1, padx=5, pady=5)
# Row 2
result_label = tk.Label(root, text="Result will appear here",
relief="sunken", width=40, anchor="center")
result_label.grid(row=2, column=0, columnspan=2, padx=5, pady=10)
# --- Start the event loop ---
root.mainloop()
The basic converter works, but we can add several features to make it more robust and user‑friendly.
We can bind the <Return> key to automatically perform the conversion based on which button is focused or based on a default direction. For simplicity, we can bind to both buttons' commands? Actually we can set focus to the entry and bind to the root.
def on_enter(event):
# By default, convert to Fahrenheit? Or let the user decide via a variable.
to_fahrenheit() # Or to_celsius – we can add a radio button for direction.
root.bind('<Return>', on_enter)
Better: We can set the default conversion direction via a Radiobutton (see extension).
A button that clears the entry and resets the result label.
def clear_all():
entry.delete(0, tk.END)
result_label.config(text="Result will appear here")
btn_clear = tk.Button(root, text="Clear", command=clear_all)
btn_clear.grid(row=1, column=2, padx=5, pady=5) # Need to adjust columns (maybe add extra column)
We would need to adjust the grid: move the two buttons to columns 0 and 1, and clear button in column 2 with columnspan adjustments.
Allow the user to choose which conversion they want, then a single "Convert" button.
direction_var = tk.StringVar(value="C_to_F")
tk.Radiobutton(root, text="C → F", variable=direction_var, value="C_to_F").grid(row=1, column=0)
tk.Radiobutton(root, text="F → C", variable=direction_var, value="F_to_C").grid(row=1, column=1)
def convert():
if direction_var.get() == "C_to_F":
to_fahrenheit()
else:
to_celsius()
btn_convert = tk.Button(root, text="Convert", command=convert)
btn_convert.grid(row=2, column=0, columnspan=2)
validate and validatecommand)We can prevent non‑numeric input from being typed at all (except for minus sign and decimal point). This is more advanced and can be done with the validate option.
def validate_input(char, current_text):
# Allow empty, digits, a single minus at start, and a single dot.
if char == "":
return True
# Only allow digits, '-', '.'
if char in "0123456789.-":
# Ensure only one '-' and only at start
if char == "-" and (current_text != "" or "-" in current_text):
return False
# Ensure only one '.'
if char == "." and current_text.count(".") >= 1:
return False
return True
return False
vcmd = (root.register(validate_input), "%S", "%P")
entry.config(validate="key", validatecommand=vcmd)
This makes the entry reject invalid characters immediately.
Store the last few conversions in a Listbox or Text widget.
history_list = tk.Listbox(root, height=4)
history_list.grid(row=3, column=0, columnspan=2, padx=5, pady=5)
def log_conversion(original, converted, direction):
history_list.insert(tk.END, f"{original} → {converted} ({direction})")
if history_list.size() > 10: # limit history
history_list.delete(0)
Call log_conversion inside to_fahrenheit and to_celsius.
Add a button that copies the result to the system clipboard.
def copy_result():
root.clipboard_clear()
root.clipboard_append(result_label.cget("text"))
float(entry.get()) inside a try block?
columnspan=2 option in the result label's grid() call?
root.bind('<Return>', lambda e: to_fahrenheit())entry.bind('<Key>', to_fahrenheit)btn_f.bind('<Button-1>', to_fahrenheit)root.config(command=to_fahrenheit)validatecommand, which option must be set on the Entry?
validate="key"validatecommand=vcmdvalidate="key" and validatecommand=vcmdentry.config(validation=True)try‑except block is unnecessary because users always enter valid numbers.grid() cannot be mixed with other geometry managers in the same container.root.clipboard_append() followed by root.clipboard_clear().relief="sunken" option makes the label look like a display screen.F = C * ____ + 32.Entry widget is .____().grid(), we use the option ________=2.tk.END is often used with entry.delete(0, tk.____) to clear all text.<________>.Modify the basic temperature converter to include a "Clear" button. It should:
Entry widget.<Return> key to convert to Fahrenheit (or to the direction selected by a radiobutton—see Exercise 3).<Escape> key to clear the entry and reset the label.Replace the two buttons with:
Radiobutton widgets to choose conversion direction (C→F or F→C).StringVar to hold the selected direction.validatecommandImplement real‑time input validation so that the entry only accepts numbers, a single minus sign (only at the start), and a single decimal point. Use the validate="key" and validatecommand approach.
Add a Listbox below the result that shows the last 5 conversions. Each entry should show the original and converted values (e.g., "25°C = 77°F"). Automatically add to the list after each conversion.
Demonstrate your ability to enhance a given GUI application by adding new features, improving usability, and handling edge cases. You will also reflect on the design process.
Build upon the basic temperature converter to create a fully featured application with the following specifications:
Core Requirements:
Entry widget for the temperature value.Radiobutton group with two options: "Celsius → Fahrenheit" and "Fahrenheit → Celsius". Default to "C → F".Label (sunken relief) that displays the result with two decimal places, e.g., "25.00°C = 77.00°F".Additional Features:
Enter (Return) to trigger conversion.Escape to clear the entry and result.Listbox that stores the last 10 conversions (oldest at top, newest at bottom). Each history item should be like "25.0°C → 77.0°F".Spinbox (or Scale) that lets the user choose the number of decimal places (0 to 4). The result should respect this setting.validatecommand to allow only digits, one minus (at start), and one decimal point.Layout Requirements:
grid() with appropriate padding.Frame containers to group related widgets (e.g., one for conversion controls, one for history).resizable(True, True)) and that the history Listbox expands when the window grows.The following code is a partial implementation of a converter that tries to use place() but has layout and logical issues. Identify four distinct problems (not syntax errors, but design/logic flaws), explain each, and suggest a fix.
import tkinter as tk
root = tk.Tk()
root.title("Bad Converter")
entry = tk.Entry(root, width=10)
entry.place(x=50, y=20)
def convert():
try:
c = float(entry.get())
f = c * 9/5 + 32
label.config(text=f) # Problem: No unit indication
except:
label.config(text="Error")
label = tk.Label(root, text="Result")
label.place(x=50, y=70)
btn = tk.Button(root, text="Convert", command=convert)
btn.place(x=50, y=120)
root.mainloop()
Your answer should list each problem and propose a concrete correction.
Provide a test plan for the temperature converter you built in Part A. Write a table with:
Include at least 5 test cases covering normal, edge (e.g., -40, 1000), and invalid inputs (empty, letters, multiple decimals).
Write a short essay (5‑7 sentences) answering: "Why is it beneficial to sketch the layout on paper before writing any code for a GUI application? How did the grid layout help in this specific project?" Relate it to the temperature converter development.
Add support for Kelvin conversion. Add a new radiobutton option for "Celsius ↔ Kelvin" (or a separate set). Modify the conversion functions to handle the new unit. Ensure all existing features (history, precision, etc.) work for the new conversion.
| Term | Definition |
|---|---|
| GUI Application | A program with a graphical user interface that responds to user events. |
| Requirements Analysis | The process of gathering and defining what the application should do. |
| Layout Sketch | A rough drawing of where widgets will be placed before coding. |
| Grid Geometry Manager | A layout system that arranges widgets in a table of rows and columns. |
| Callback | A function attached to an event (like a button click) that executes when the event occurs. |
| Error Handling | Using try/except to catch and manage runtime errors (e.g., invalid input). |
| Validation | Checking user input for correctness (e.g., numeric only). |
| History | A log of previous actions or results, often displayed in a Listbox. |
| Clipboard | A system‑wide storage for copied text; accessible via clipboard_append() and clipboard_clear(). |
validate option in Tkinter for more complex validation.This tutorial is designed to take approximately 3 hours of study, lab work, and homework. The temperature converter, though simple, is a perfect sandbox to practice all the core Tkinter concepts you have learned so far.