DEV Community

Mate Technologies
Mate Technologies

Posted on

Building a Modern Unit Converter in Python with Tkinter

In this tutorial, we’ll create a Unit Converter desktop application using Python and Tkinter. The app can convert between common length and weight units, includes a conversion history, and supports light/dark themes.

We'll walk through the complete source code and explain each part so you can build your own version.

Features

Convert Length (meters, kilometers, miles, etc.)

Convert Weight (kilograms, grams, pounds, etc.)

Maintains last 10 conversions

Dark Mode toggle for comfortable usage

Modern UI using ttk and sv_ttk themes

Prerequisites

Make sure you have Python 3.x installed. We'll also use sv_ttk for a nicer look and feel.

Install sv_ttk:

pip install sv_ttk

Full Source Code

import sys
import os
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import sv_ttk

# Helper to get resource path (for PyInstaller)
def resource_path(file_name):
    base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, file_name)

# =========================
# App Setup
# =========================
root = tk.Tk()
root.title("Unit Converter")
root.geometry("1100x800")
# root.iconbitmap(resource_path("logo.ico"))

sv_ttk.set_theme("light")

# =========================
# Globals
# =========================
dark_mode_var = tk.BooleanVar(value=False)
history_data = []  # Stores last 10 conversions

# =========================
# Status Bar
# =========================
status_var = tk.StringVar(value="Ready")
ttk.Label(root, textvariable=status_var, anchor="w", font=("Segoe UI", 10)).pack(side=tk.BOTTOM, fill="x")

# =========================
# Theme Toggle Function
# =========================
style = ttk.Style()
style.theme_use("clam")

def set_status(msg):
    status_var.set(msg)
    root.update_idletasks()

def toggle_theme():
    if dark_mode_var.get():
        root.configure(bg="#2E2E2E")
        style.configure("TLabel", background="#2E2E2E", foreground="white")
        style.configure("TFrame", background="#2E2E2E")
        style.configure("TNotebook.Tab", background="#444444", foreground="white")
        style.map("TNotebook.Tab", background=[("selected", "#90caf9")], foreground=[("selected", "black")])
    else:
        root.configure(bg="#FFFFFF")
        style.configure("TLabel", background="#FFFFFF", foreground="black")
        style.configure("TFrame", background="#FFFFFF")
        style.configure("TNotebook.Tab", background="#e0e0e0", foreground="black")
        style.map("TNotebook.Tab", background=[("selected", "#90caf9")], foreground=[("selected", "black")])
    set_status(f"Theme switched to {'Dark' if dark_mode_var.get() else 'Light'} mode")

# =========================
# Notebook Tabs
# =========================
tabs = ttk.Notebook(root)
tabs.pack(expand=True, fill="both", padx=20, pady=20)

# Dashboard Tab
dash_tab = ttk.Frame(tabs, padding=20)
tabs.add(dash_tab, text="🏠 Dashboard")
ttk.Label(dash_tab, text="Unit Converter", font=("Segoe UI", 20, "bold")).pack(anchor="w")
ttk.Label(dash_tab, text="Convert between Length and Weight units quickly and accurately.", font=("Segoe UI", 11)).pack(anchor="w", pady=(5,10))

# Converter Tab
tool_tab = ttk.Frame(tabs, padding=20)
tabs.add(tool_tab, text="🔧 Converter")

# Conversion Factors
length_units = {
    "Meters": 1.0, "Kilometers": 1000.0, "Centimeters": 0.01, "Millimeters": 0.001,
    "Miles": 1609.34, "Yards": 0.9144, "Feet": 0.3048, "Inches": 0.0254
}

weight_units = {
    "Kilograms": 1.0, "Grams": 0.001, "Milligrams": 0.000001,
    "Pounds": 0.453592, "Ounces": 0.0283495
}

# UI Elements
ttk.Label(tool_tab, text="Value to Convert:", font=("Segoe UI", 12)).pack(anchor="w", pady=5)
value_entry = ttk.Entry(tool_tab, font=("Segoe UI", 14))
value_entry.pack(fill="x", pady=5)

ttk.Label(tool_tab, text="Select Category:", font=("Segoe UI", 12)).pack(anchor="w", pady=5)
category_var = tk.StringVar(value="Length")
category_combo = ttk.Combobox(tool_tab, textvariable=category_var, state="readonly",
                              values=["Length", "Weight"], font=("Segoe UI", 12))
category_combo.pack(fill="x", pady=5)

ttk.Label(tool_tab, text="From Unit:", font=("Segoe UI", 12)).pack(anchor="w", pady=5)
from_unit_var = tk.StringVar(value="Meters")
from_combo = ttk.Combobox(tool_tab, textvariable=from_unit_var, state="readonly", font=("Segoe UI", 12))
from_combo.pack(fill="x", pady=5)

ttk.Label(tool_tab, text="To Unit:", font=("Segoe UI", 12)).pack(anchor="w", pady=5)
to_unit_var = tk.StringVar(value="Kilometers")
to_combo = ttk.Combobox(tool_tab, textvariable=to_unit_var, state="readonly", font=("Segoe UI", 12))
to_combo.pack(fill="x", pady=5)

result_var = tk.StringVar(value="")
ttk.Label(tool_tab, text="Result:", font=("Segoe UI", 12)).pack(anchor="w", pady=5)
ttk.Label(tool_tab, textvariable=result_var, font=("Segoe UI", 14, "bold")).pack(anchor="w", pady=5)

# History Frame
history_frame = ttk.LabelFrame(tool_tab, text="Conversion History (Last 10)", padding=10)
history_frame.pack(fill="both", expand=True, pady=10)
history_text = scrolledtext.ScrolledText(history_frame, height=6, font=("Consolas", 11), state="disabled")
history_text.pack(fill="both", expand=True)

# =========================
# Functions
# =========================
def update_units(event=None):
    units = list(length_units.keys()) if category_var.get() == "Length" else list(weight_units.keys())
    from_combo["values"] = units
    to_combo["values"] = units
    from_combo.set(units[0])
    to_combo.set(units[1])

def convert_unit():
    try:
        value = float(value_entry.get())
        factor_from = length_units[from_unit_var.get()] if category_var.get() == "Length" else weight_units[from_unit_var.get()]
        factor_to = length_units[to_unit_var.get()] if category_var.get() == "Length" else weight_units[to_unit_var.get()]
        result = value * factor_from / factor_to
        result_var.set(f"{result:.6g}")
        set_status(f"Converted {value} {from_unit_var.get()} to {result:.6g} {to_unit_var.get()}")
        add_to_history(value, from_unit_var.get(), result, to_unit_var.get())
    except ValueError:
        messagebox.showerror("Error", "Please enter a valid number")
        result_var.set("")
        set_status("Error: Invalid input")

def add_to_history(value, from_unit, result, to_unit):
    global history_data
    entry = f"{value} {from_unit} → {result:.6g} {to_unit}"
    history_data.insert(0, entry)
    history_data = history_data[:10]
    history_text.config(state="normal")
    history_text.delete("1.0", tk.END)
    history_text.insert(tk.END, "\n".join(history_data))
    history_text.config(state="disabled")

# Bind events
category_combo.bind("<<ComboboxSelected>>", update_units)

# Buttons
ttk.Button(tool_tab, text="Convert", command=convert_unit, style="Action.TButton").pack(pady=10)
ttk.Checkbutton(root, text="Dark Mode", variable=dark_mode_var, command=toggle_theme).pack(side="bottom", pady=5)

# Initialize
update_units()
root.mainloop()
Enter fullscreen mode Exit fullscreen mode

How It Works

UI Setup: We use ttk.Notebook for tabs (Dashboard & Converter).

Unit Conversion: Conversion factors are stored in dictionaries (length_units and weight_units).

History: Last 10 conversions are displayed in a ScrolledText widget.

Dark Mode: Toggle modifies the ttk.Style and background colors dynamically.

Error Handling: Invalid inputs are caught with a messagebox.

Screenshots

Building a Modern Unit Converter in Python with Tkinter

Conclusion

This project demonstrates how Tkinter can be used to create modern, functional desktop apps in Python with nice UI and features like history and dark mode.

Top comments (0)