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.
Praktyka: Tip Calculator Pro
Moduł tkinter otwiera natywne okno Python — wykonaj lokalnie w VS Code, PyCharm lub Jupyter
Praktyka kursuje lokalnie