DEV Community

Mate Technologies
Mate Technologies

Posted on

🧮 Building an Interactive Tip Calculator with Tkinter (Step-by-Step)

In this tutorial, we’ll build a modern, animated tip calculator desktop app using Python + Tkinter.

By the end, you’ll have:

Real-time tip calculations

Animated number updates

Dark mode toggle

Preset tip buttons

Calculation history with export

A polished UI using ttk and sv_ttk

No advanced Tkinter knowledge required—we’ll build it piece by piece.

🧰 Step 1: Import Required Modules

Let’s start by importing everything we need.

import sys
import os
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import sv_ttk
Enter fullscreen mode Exit fullscreen mode

Why these imports?

tkinter → GUI framework

ttk → modern themed widgets

messagebox / filedialog → dialogs & file saving

sv_ttk → clean light/dark themes

sys / os → handle packaged app paths (PyInstaller-friendly)

📁 Step 2: Helper Functions
2.1 Loading files safely (for packaging)

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)
Enter fullscreen mode Exit fullscreen mode

This ensures your app works both normally and when bundled into an .exe.

2.2 Status bar helper

def set_status(msg):
    status_var.set(msg)
    root.update_idletasks()
Enter fullscreen mode Exit fullscreen mode

We’ll use this to show messages like:

“Calculation completed!”
“History exported successfully”

🪟 Step 3: Create the Main App Window

root = tk.Tk()
root.title("Interactive Tip Calculator")
root.geometry("600x700")
root.minsize(600, 700)

sv_ttk.set_theme("light")
Enter fullscreen mode Exit fullscreen mode

What’s happening?

We create the main window

Set a minimum size (prevents layout breakage)

Apply a modern light theme

📦 Step 4: Define App State (Global Variables)

dark_mode_var = tk.BooleanVar(value=False)

bill_var = tk.StringVar()
tip_var = tk.StringVar(value="15")
people_var = tk.StringVar(value="1")

tip_per_person_var = tk.StringVar(value="$0.00")
total_per_person_var = tk.StringVar(value="$0.00")

calculation_history = []
Enter fullscreen mode Exit fullscreen mode

Why StringVar?

They automatically update the UI when their value changes—perfect for real-time apps.

🌗 Step 5: Dark Mode Toggle

def toggle_theme():
    bg = "#2E2E2E" if dark_mode_var.get() else "#FFFFFF"
    fg = "white" if dark_mode_var.get() else "black"

    root.configure(bg=bg)

    for w in ["TFrame", "TLabel", "TLabelframe", "TLabelframe.Label", "TCheckbutton"]:
        style.configure(w, background=bg, foreground=fg)

    for entry in [bill_entry, tip_entry, people_entry]:
        entry.configure(background=bg, foreground=fg)
Enter fullscreen mode Exit fullscreen mode

This:

Switches colors dynamically

Updates both ttk widgets and normal Entry fields

💰 Step 6: Tip Calculation Logic

def calculate_tip(real_time=False):
    try:
        bill = float(bill_var.get())
        tip_percent = float(tip_var.get())
        people = int(people_var.get())

        if bill < 0 or tip_percent < 0 or people < 1:
            raise ValueError
    except ValueError:
        if not real_time:
            messagebox.showerror("Invalid Input", "Please enter valid numeric values.")
        return
Enter fullscreen mode Exit fullscreen mode

Why validation matters

We prevent:

Negative numbers

Division by zero

Non-numeric input

6.1 Perform the calculation

    tip_total = bill * (tip_percent / 100)
    total_bill = bill + tip_total

    tip_per_person = tip_total / people
    total_per_person = total_bill / people
Enter fullscreen mode Exit fullscreen mode

6.2 Animate the result

    animate_split(tip_per_person, total_per_person)
Enter fullscreen mode Exit fullscreen mode

Instead of instantly updating numbers, we animate them for a smoother UX.

🎞 Step 7: Animated Number Updates

def animate_split(tip_final, total_final, steps=30, interval=15):
    tip_current = 0.0
    total_current = 0.0

    tip_step = tip_final / steps
    total_step = total_final / steps
Enter fullscreen mode Exit fullscreen mode

We gradually increase values in small steps.

7.1 Animation loop

    def step_animation(count=0):
        nonlocal tip_current, total_current

        if count >= steps:
            tip_per_person_var.set(f"${tip_final:.2f}")
            total_per_person_var.set(f"${total_final:.2f}")
            return

        tip_current += tip_step
        total_current += total_step

        tip_per_person_var.set(f"${tip_current:.2f}")
        total_per_person_var.set(f"${total_current:.2f}")

        root.after(interval, lambda: step_animation(count + 1))
Enter fullscreen mode Exit fullscreen mode

root.after() lets Tkinter animate without freezing the UI.

⚡ Step 8: Preset Tip Buttons

def set_tip(value):
    tip_var.set(str(value))
    calculate_tip(real_time=True)
Enter fullscreen mode Exit fullscreen mode

This allows buttons like 10% / 15% / 20% to instantly update results.

🗂 Step 9: Calculation History
9.1 Save results

def add_to_history(bill, tip_percent, people, tip_pp, total_pp):
    calculation_history.append((bill, tip_percent, people, tip_pp, total_pp))

    preview = (
        f"${bill:.2f} | {tip_percent}% | {people} person(s) → "
        f"Tip: ${tip_pp:.2f}, Total: ${total_pp:.2f}"
    )

    history_list.insert(tk.END, preview)
Enter fullscreen mode Exit fullscreen mode

Each calculation is stored and shown in a list.

📤 Step 10: Export History to a Text File

def export_history_txt():
    if not calculation_history:
        messagebox.showinfo("Empty History", "No calculations to export.")
        return
Enter fullscreen mode Exit fullscreen mode

We prevent exporting empty data.

10.1 Save the file

    file_path = filedialog.asksaveasfilename(
        defaultextension=".txt",
        filetypes=[("Text Files", "*.txt")],
        title="Export Tip History"
    )
Enter fullscreen mode Exit fullscreen mode

10.2 Write the data

    with open(file_path, "w", encoding="utf-8") as f:
        f.write("Tip Calculator History\n")
        f.write("=" * 40 + "\n\n")

        for i, (bill, tip, people, tip_pp, total_pp) in enumerate(calculation_history, 1):
            f.write(f"{i}. Bill: ${bill:.2f}, Tip: {tip}%, People: {people}\n")
            f.write(f"   Tip per person: ${tip_pp:.2f}, Total per person: ${total_pp:.2f}\n\n")
Enter fullscreen mode Exit fullscreen mode

🖱 Step 11: Double-Click History Viewer (Animated)

When a history item is double-clicked, we open a detail window and type the content line-by-line.

This section introduces:

Toplevel windows

Text widgets

Typing animations

(You already have excellent code here—great example of UI polish.)

🎨 Step 12: Styling

style = ttk.Style()
style.theme_use("clam")

style.configure(
    "Action.TButton",
    font=("Segoe UI", 11, "bold"),
    padding=8
)
Enter fullscreen mode Exit fullscreen mode

Custom styles make your app feel intentional and professional.

🧱 Step 13: Layout the Interface

We use:

Frame containers

grid() for structure

pack() only when appropriate

Each section (inputs, outputs, buttons, history) is clearly separated—perfect for maintainability.

🔄 Step 14: Real-Time Updates

bill_var.trace_add("write", lambda *args: calculate_tip(real_time=True))
tip_var.trace_add("write", lambda *args: calculate_tip(real_time=True))
people_var.trace_add("write", lambda *args: calculate_tip(real_time=True))
Enter fullscreen mode Exit fullscreen mode

This makes the calculator react instantly as the user types.

▶️ Step 15: Run the App

root.mainloop()
Enter fullscreen mode Exit fullscreen mode

This starts Tkinter’s event loop—and your app comes to life 🎉

🏁 Final Thoughts

You now have:

A polished Tkinter desktop app

Animations without threading headaches

A real-time, user-friendly experience

A great foundation for packaging with PyInstaller

Interactive Tip Calculator

Top comments (0)