Rozdział 16 · Tworzenie fajnych aplikacji z Tkinter

Tip Calculator Pro: wersja ostateczna

Ten sam projekt co w sekcji 16.8 — ale teraz, mając wszystko, czego się od tamtej pory nauczyliśmy.

Wróćmy do kalkulatora napiwków (sekcja 16.8) i zbierzmy wszystko, czego się nauczyliśmy w tym Rozdział — etapami, a nie jednym wielkim, ostatnim kawałkiem kodu.

Droga od fundacji do Pro
V1–V2 (16.8)
Entry + Button + grid()
czysta formuła napiwków
V3
Combobox z typowymi procentami zamiast Entry
V4
pole „liczba osób”
V5
validate_positive_amount/validate_positive_int (16.23)
V6
menu plików z Exit (16.6)
V7
load_settings/save_settings — zapamiętujemy ostatni procent (16.25)
V8
klasa TipCalculatorApp — wszystko w jednym obiekcie (16.25)
Ustawienia są zapisywane względem aktualnego katalogu roboczego
SETTINGS_PATH = Path("tip_calculator_settings.json") jest ścieżka względna: plik pojawi się tam, gdzie program został uruchomiony (Rozdział 15, Rozdział 15.7 o CWD). Dla projektu uczącego się jest to świadomy, wyraźny wybór — nie zapomniana niepewność.
tip_calculator_pro.py
import json
import tkinter as tk
from tkinter import ttk
from pathlib import Path

SETTINGS_PATH = Path("tip_calculator_settings.json")
DEFAULT_SETTINGS = {"last_percent": "15"}

def load_settings():
    if not SETTINGS_PATH.exists():
        return dict(DEFAULT_SETTINGS)
    with SETTINGS_PATH.open("r", encoding="utf-8") as f:
        return json.load(f)

def save_settings(settings):
    with SETTINGS_PATH.open("w", encoding="utf-8") as f:
        json.dump(settings, f, ensure_ascii=False, indent=2)

def parse_number(text):
    text = text.strip()
    if not text:
        return False, None, „Pole nie może być puste"
    try:
        return True, float(text), ""
    except ValueError:
        return False, None, „Wprowadź numer”

def validate_positive_amount(text):
    ok, value, message = parse_number(text)
    if not ok:
        return False, message
    if value <= 0:
        return False, „Liczba musi być większa od zera"
    return True, ""

def validate_positive_int(text):
    ok, value, message = parse_number(text)
    if not ok:
        return False, message
    if value != int(value):
        return False, „Wprowadź liczbę całkowitą”
    if int(value) < 1:
        return False, „Liczba musi wynosić co najmniej 1”
    return True, ""

def calculate_tip(amount, percent, people):
    return (amount * percent / 100) / people

class TipCalculatorApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Tip Calculator Pro")
        self.settings = load_settings()
        self.amount_var = tk.StringVar()
        self.percent_var = tk.StringVar(value=self.settings["last_percent"])
        self.people_var = tk.StringVar(value="1")
        self.result_var = tk.StringVar()
        self.build_ui()
        self.root.protocol("WM_DELETE_WINDOW", self.on_close)

    def build_ui(self):
        frame = ttk.Frame(self.root, padding=12)
        frame.grid(row=0, column=0, sticky="nsew")
        self.root.columnconfigure(0, weight=1)

        ttk.Label(frame, text=„Suma konta:”).grid(row=0, column=0, sticky="w")
        ttk.Entry(frame, textvariable=self.amount_var).grid(row=0, column=1, sticky="ew")

        ttk.Label(frame, text=„Procent napiwków:”).grid(row=1, column=0, sticky="w")
        ttk.Combobox(frame, textvariable=self.percent_var, values=["10", "15", "20"],
            state="readonly").grid(row=1, column=1, sticky="ew")

        ttk.Label(frame, text=„Liczba osób:”).grid(row=2, column=0, sticky="w")
        ttk.Entry(frame, textvariable=self.people_var).grid(row=2, column=1, sticky="ew")

        ttk.Button(frame, text=„Obliczaj”, command=self.on_calculate).grid(
            row=3, column=0, columnspan=2, pady=8)
        ttk.Label(frame, textvariable=self.result_var).grid(row=4, column=0, columnspan=2)
        frame.columnconfigure(1, weight=1)

    def on_calculate(self):
        ok, message = validate_positive_amount(self.amount_var.get())
        if not ok:
            self.result_var.set(message)
            return
        ok_people, message_people = validate_positive_int(self.people_var.get())
        if not ok_people:
            self.result_var.set(„Liczba osób: ” + message_people)
            return
        chaevye = calculate_tip(
            float(self.amount_var.get()),
            float(self.percent_var.get()),
            int(self.people_var.get()),
        )
        self.result_var.set(f"napiwki na osobę: {chaevye:.2f}")
        self.settings["last_percent"] = self.percent_var.get()

    def on_close(self):
        save_settings(self.settings)
        self.root.destroy()

root = tk.Tk()
app = TipCalculatorApp(root)
root.mainloop()
Liczba osób jest dodatnią liczbą całkowitą, a nie dowolną
nie ma 2,5 osoby. validate_positive_int (Rozdział 16.23) przyjmuje 1, 2, 5, 10 — i odrzucone 0, -1, 2.5, "abc" i pusta linia z wyraźnym komunikatem o błędzie.
Okno Tip Calculator Pro z sumą rachunku 1000, procentem 15, liczbą osób 2 i wynikiem 75.00
Wyjściem powyższego kodu jest prawdziwe okno, a nie ilustracja. Przykład wyglądu w środowisku kursu. Czcionki, rozmiary i motywy mogą się nieco różnić w zależności od systemu operacyjnego.
app : TipCalculatorApproot = Tksettings = dictamount_var = StringVarpercent_var = StringVarpeople_var = StringVarresult_var = StringVar
Ostateczny graf obiektowy: jedna aplikacja, jawne zależności, brak zmiennych globalnych.
Praktyka: Tip Calculator Pro
Moduł tkinter otwiera natywne okno Python — wykonaj lokalnie w VS Code, PyCharm lub Jupyter
Praktyka kursuje lokalnie
Otwórz praktykę →