Aufgaben-Python/14_gui_mit_tkinter/gui_app.py

314 lines
9.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Kapitel 14: Grafische Desktop-Anwendung (Startbares Skript) 🖥️✨
================================================================
Führe diese Datei auf deinem Desktop aus (z.B. in VS Code auf Mac/Windows/Linux):
python3 gui_app.py
Features dieser erweiterten Version:
- Saubere Model-View-Controller (MVC) Architektur
- Farb-Feedback (Grün bei positiven, Rot bei negativen Zahlen)
- Tastatur-Shortcuts:
[Pfeil nach oben] -> Erhöhen
[Pfeil nach unten] -> Verringern
[R] -> Zurücksetzen
- Warnung bei Grenzwert-Überschreitungen
"""
import sys
from typing import Optional
try:
import tkinter as tk
from tkinter import messagebox
TK_AVAILABLE = True
except ImportError:
TK_AVAILABLE = False
class _MockWidget:
def __init__(self, *args, **kwargs): pass
def pack(self, *args, **kwargs): pass
def grid(self, *args, **kwargs): pass
def columnconfigure(self, *args, **kwargs): pass
def bind(self, *args, **kwargs): pass
def config(self, *args, **kwargs): pass
def insert(self, *args, **kwargs): pass
def delete(self, *args, **kwargs): pass
def get(self): return "1"
class _MockTkModule:
Tk = _MockWidget
Label = _MockWidget
Button = _MockWidget
Entry = _MockWidget
Frame = _MockWidget
LabelFrame = _MockWidget
tk = _MockTkModule()
messagebox = None
class ZaehlerLogik:
"""Modell für Zähler, Grenzwerte und Schrittweite."""
def __init__(self, startwert: int = 0, min_wert: int = -100, max_wert: int = 100):
self.wert: int = startwert
self.min_wert: int = min_wert
self.max_wert: int = max_wert
self.schrittweite: int = 1
self.klick_anzahl: int = 0
def erhoehen(self) -> int:
self.klick_anzahl += 1
self.wert = min(self.max_wert, self.wert + self.schrittweite)
return self.wert
def verringern(self) -> int:
self.klick_anzahl += 1
self.wert = max(self.min_wert, self.wert - self.schrittweite)
return self.wert
def zuruecksetzen(self) -> int:
self.klick_anzahl += 1
self.wert = 0
return self.wert
def setze_schrittweite(self, schritt: int) -> bool:
if schritt > 0:
self.schrittweite = schritt
return True
return False
def ist_gerade(self) -> bool:
return self.wert % 2 == 0
def __str__(self) -> str:
return f"Zählerstand: {self.wert} (Schritt: {self.schrittweite})"
class DesktopZaehlerGUI:
"""Moderne grafische Desktop-Benutzeroberfläche mit Tkinter."""
def __init__(self, root: tk.Tk):
self.root = root
self.root.title("Python OOP • Desktop Zähler 🔢")
self.root.geometry("420x440")
self.root.minsize(360, 400)
self.root.configure(bg="#f8fafc")
self.logik = ZaehlerLogik(startwert=0, min_wert=-100, max_wert=100)
# UI Komponenten aufbauen
self._erstelle_widgets()
self._binde_tasten()
self.aktualisiere_anzeige()
def _erstelle_widgets(self):
# 1. Header Frame
frame_header = tk.Frame(self.root, bg="#4f46e5", pady=14)
frame_header.pack(fill="x")
self.label_titel = tk.Label(
frame_header,
text="🔢 Python Desktop Zähler",
font=("Helvetica", 16, "bold"),
bg="#4f46e5",
fg="white",
)
self.label_titel.pack()
self.label_sub = tk.Label(
frame_header,
text="Kapitel 14 • Tkinter & OOP",
font=("Helvetica", 9),
bg="#4f46e5",
fg="#c7d2fe",
)
self.label_sub.pack()
# 2. Haupt-Anzeige Frame
frame_card = tk.Frame(self.root, bg="#ffffff", bd=1, relief="solid", padx=20, pady=15)
frame_card.pack(fill="x", padx=25, pady=15)
self.label_anzeige = tk.Label(
frame_card,
text="0",
font=("Helvetica", 42, "bold"),
bg="#ffffff",
fg="#4f46e5",
)
self.label_anzeige.pack(pady=(5, 0))
self.label_info = tk.Label(
frame_card,
text="Klicks: 0 | Gerade Zahl",
font=("Helvetica", 10),
bg="#ffffff",
fg="#64748b",
)
self.label_info.pack(pady=(0, 5))
# 3. Button Steuerungsbereich
frame_buttons = tk.Frame(self.root, bg="#f8fafc")
frame_buttons.pack(fill="x", padx=25, pady=5)
frame_buttons.columnconfigure(0, weight=1)
frame_buttons.columnconfigure(1, weight=1)
self.btn_plus = tk.Button(
frame_buttons,
text="➕ Erhöhen [↑]",
font=("Helvetica", 11, "bold"),
bg="#10b981",
fg="white",
activebackground="#059669",
activeforeground="white",
relief="flat",
pady=8,
cursor="hand2",
command=self.klick_plus,
)
self.btn_plus.grid(row=0, column=0, padx=5, pady=5, sticky="ew")
self.btn_minus = tk.Button(
frame_buttons,
text="➖ Verringern [↓]",
font=("Helvetica", 11, "bold"),
bg="#ef4444",
fg="white",
activebackground="#dc2626",
activeforeground="white",
relief="flat",
pady=8,
cursor="hand2",
command=self.klick_minus,
)
self.btn_minus.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
self.btn_reset = tk.Button(
frame_buttons,
text="↺ Zurücksetzen [R]",
font=("Helvetica", 10),
bg="#e2e8f0",
fg="#1e293b",
activebackground="#cbd5e1",
relief="flat",
pady=6,
cursor="hand2",
command=self.klick_reset,
)
self.btn_reset.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky="ew")
# 4. Schrittweite Konfiguration
frame_settings = tk.LabelFrame(
self.root,
text=" ⚙️ Einstellungen ",
font=("Helvetica", 9, "bold"),
bg="#f8fafc",
fg="#475569",
padx=10,
pady=8,
)
frame_settings.pack(fill="x", padx=25, pady=10)
tk.Label(frame_settings, text="Schrittweite:", bg="#f8fafc", font=("Helvetica", 10)).pack(
side="left", padx=5
)
self.entry_schritt = tk.Entry(
frame_settings, width=6, font=("Helvetica", 11), justify="center"
)
self.entry_schritt.insert(0, "1")
self.entry_schritt.pack(side="left", padx=5)
self.btn_schritt = tk.Button(
frame_settings,
text="Übernehmen",
font=("Helvetica", 9),
bg="#6366f1",
fg="white",
relief="flat",
cursor="hand2",
command=self.klick_schritt_setzen,
)
self.btn_schritt.pack(side="left", padx=5)
def _binde_tasten(self):
"""Bindet bequeme Tastatur-Shortcuts an das Fenster."""
self.root.bind("<Up>", lambda event: self.klick_plus())
self.root.bind("<Down>", lambda event: self.klick_minus())
self.root.bind("<r>", lambda event: self.klick_reset())
self.root.bind("<R>", lambda event: self.klick_reset())
self.root.bind("<Return>", lambda event: self.klick_schritt_setzen())
def klick_plus(self) -> None:
self.logik.erhoehen()
self.aktualisiere_anzeige()
def klick_minus(self) -> None:
self.logik.verringern()
self.aktualisiere_anzeige()
def klick_reset(self) -> None:
self.logik.zuruecksetzen()
self.aktualisiere_anzeige()
def klick_schritt_setzen(self) -> None:
eingabe = self.entry_schritt.get().strip()
try:
schritt = int(eingabe)
if self.logik.setze_schrittweite(schritt):
self.aktualisiere_anzeige()
else:
self.label_info.config(
text="⚠️ Schrittweite muss größer als 0 sein!", fg="#ef4444"
)
except ValueError:
self.label_info.config(
text="⚠️ Bitte eine ganze Zahl eingeben!", fg="#ef4444"
)
def aktualisiere_anzeige(self) -> None:
# Zählerwert darstellen
self.label_anzeige.config(text=str(self.logik.wert))
# Farb-Feedback
if self.logik.wert > 0:
self.label_anzeige.config(fg="#10b981") # Grün
elif self.logik.wert < 0:
self.label_anzeige.config(fg="#ef4444") # Rot
else:
self.label_anzeige.config(fg="#4f46e5") # Neutral
# Info-Text
paritaet = "Gerade" if self.logik.ist_gerade() else "Ungerade"
info = f"Klicks: {self.logik.klick_anzahl} | {paritaet} | Schritt: {self.logik.schrittweite}"
if self.logik.wert >= self.logik.max_wert:
info += " ⚠️ Max erreicht!"
elif self.logik.wert <= self.logik.min_wert:
info += " ⚠️ Min erreicht!"
self.label_info.config(text=info, fg="#64748b")
def main():
if not TK_AVAILABLE:
print("=" * 60)
print("⚠️ Hinweis: Tkinter ist in dieser Linux-Umgebung nicht installiert.")
print("Um die grafische Oberfläche mit echtem Fenster zu sehen:")
print(" 1. Klone oder öffne diesen Ordner auf deinem PC/Laptop (Windows/Mac/Linux).")
print(" 2. Führe 'python3 gui_app.py' aus!")
print("=" * 60)
return
try:
root = tk.Tk()
app = DesktopZaehlerGUI(root)
print("🖥️ GUI-Fenster gestartet! Schließe das Fenster zum Beenden.")
root.mainloop()
except Exception as e:
print(f"⚠️ Konnte GUI nicht initialisieren (kein X11-Display gefunden): {e}")
print("💡 Teste deinen Code headless mit: python3 test_aufgabe.py")
if __name__ == "__main__":
main()