Kapitel 14 • Schulabgleich 27.0 Tkinter

Desktop-GUIs mit Tkinter & OOP 🖥️✨

Vom Text-Terminal zum echten Betriebssystem-Fenster mit Buttons, Eingabefeldern, Layout-Grids und ereignisgesteuerter OOP-Architektur.

📂 Dateien dieser Einheit:
💻 In Web-IDE öffnen → 📄 aufgabe.py (Startercode) 🧪 test_aufgabe.py (Tests) 💡 musterloesung.py (Lösung) 🖥️ gui_app.py 📖 README.md

1. Vom Terminal zur grafischen Oberfläche (GUI)

Bisher liefen all deine Python-Programme linear im schwarzen Terminal ab: Text einlesen mit input(), Text ausgeben mit print(). Echte Desktop-Software (wie Spotify, VS Code oder ein Taschenrechner) funktioniert jedoch völlig anders: Sie besitzt ein Fenster, das geduldig auf Mausklicks und Tastatureingaben wartet.

📺 Die Fernseh-Analogie: Vom Schwarz-Weiß-Fernseher zum Touchscreen
  • Terminal (Linear): Wie ein alter Filmstreifen. Bild für Bild läuft starr von oben nach unten ab. Am Ende ist der Film vorbei.
  • GUI (Ereignisgesteuert / Event-Driven): Wie ein interaktiver Touchscreen. Das System schläft im Hintergrund (im sogenannten Event-Loop). Sobald du auf einen Knopf drückst, feuert ein Signal und deine Methode reagiert blitzschnell darauf!

Der Herzschlag einer GUI: Der Event-Loop

Ein GUI-Programm beendet sich nicht sofort, sondern übergibt die Kontrolle an root.mainloop():

Python (Minimales Tkinter-Fenster)
import tkinter as tk

root = tk.Tk()                      # 1. Hauptfenster erzeugen
root.title("Mein erstes GUI-Fenster")  # 2. Fenstertitel festlegen
root.geometry("400x300")             # 3. Fenstergröße (Breite x Höhe)

label = tk.Label(root, text="Hallo Welt! 👋", font=("Arial", 16))
label.pack(pady=20)                  # 4. Widget im Fenster platzieren

root.mainloop()                     # 5. Ereignisschleife starten (wartet auf Events)

2. Bausteine einer GUI: Die wichtigsten Widgets

In Tkinter heißen alle visuellen Bedienelemente Widgets (Window Gadgets). Jedes Widget ist ein Python-Objekt, das wir in unserem Fenster platzieren können:

Widget Klasse Zweck Wichtige Parameter & Methoden
Hauptfenster tk.Tk() Rahmen & Titelleiste des Betriebssystems .title(), .geometry("BxH"), .mainloop()
Textanzeige tk.Label Zeigt Texte, Titel oder Zahlenwerte text="...", font=("Arial", 14), fg="color", .config(text=...)
Schaltfläche tk.Button Knopf, der beim Anklicken eine Methode ausführt text="...", command=self.klick_methode, bg="color"
Eingabefeld tk.Entry Einzeiliges Textfeld für Benutzereingaben .get(), .insert(0, text), .delete(0, "end")
Container tk.Frame Unsichtbare Box zum Gruppieren von Widgets Unterteilt komplexe Layouts in saubere Blöcke

3. Layout-Manager: Das 2D-Grid-System (Raster)

Widgets müssen auf dem Bildschirm positioniert werden. Der mächtigste und sauberste Layout-Manager in Tkinter ist grid(). Er teilt den Raum wie eine Excel-Tabelle oder ein Schachbrett in Zeilen (row) und Spalten (column) ein:

row=0, column=0, columnspan=2 → 🔢 Titel: "Python Klick-Zähler"
row=1, column=0, columnspan=2 → Zählerstand: "0"
row=2, column=0, columnspan=2 → Status-Info: "Klicks: 0 | Gerade Zahl"
row=3, column=0
➕ Erhöhen
row=3, column=1
➖ Verringern
row=4, column=0, columnspan=2 → ↺ Zurücksetzen (Reset)
⚠️ Häufigster Anfängerfehler: .pack() und .grid() niemals mischen!
Verwende innerhalb eines Fensters (oder innerhalb eines Frames) entweder nur grid() oder nur pack(). Wenn du beide Layout-Manager im selben übergeordneten Fenster mischst, friert Python in einer Endlosschleife ein!

4. Event-Driven Programming: Was geschieht beim Klick?

Wenn der Benutzer auf einen Button klickt, soll ein Stück Code ausgeführt werden. In Tkinter übergeben wir dafür das command-Attribut:

Python: Button-Befehle richtig übergeben
# ✅ RICHTIG: Nur den Methodennamen übergeben (OHNE Klammern!)
self.btn_plus = tk.Button(self.root, text="➕", command=self.klick_plus)

# ❌ FALSCH: Mit Klammern wird die Methode SOFORT beim Erstellen aufgerufen!
self.btn_plus = tk.Button(self.root, text="➕", command=self.klick_plus())
💡 Warum ohne Klammern?
Mit self.klick_plus übergibst du dem Button eine Visitenkarte (Referenz) der Methode. Der Button behält diese Visitenkarte und ruft die Methode erst dann an, wenn der Benutzer wirklich mit der Maus daraufklickt.

5. Saubere Software-Architektur: Model-View-Trennung (MVC) 🏛️

In der professionellen Entwicklung schreibt man GUI-Apps niemals als wilden Spaghetticode, sondern trennt die Logik (Berechnung & Daten) strikt von der Präsentation (Fenster & Buttons):

1. Model: ZaehlerLogik

Reines Python! Verwaltet wert, schrittweite, Grenzwerte und Berechnungen.
Vorteil: 100% unabhängig von Bildschirmen und in Sekundenbruchteilen per Unit Test prüfbar!

2. View / Controller: ZaehlerApp

Hält die Widgets (Label, Button, Entry).
Bei einem Button-Klick ruft sie Methoden der ZaehlerLogik auf und spiegelt das Ergebnis im Label wider.

Struktur einer OOP-GUI-Klasse
class ZaehlerApp:
    def __init__(self, root: tk.Tk):
        self.root = root
        self.root.title("Python Zähler")
        
        # 1. Datenmodell einbinden
        self.logik = ZaehlerLogik()

        # 2. Widgets erstellen
        self.label_anzeige = tk.Label(self.root, text="0", font=("Arial", 32))
        self.label_anzeige.grid(row=0, column=0)

        self.btn_plus = tk.Button(self.root, text="➕", command=self.klick_plus)
        self.btn_plus.grid(row=1, column=0)

    def klick_plus(self) -> None:
        self.logik.erhoehen()
        self.aktualisiere_anzeige()

    def aktualisiere_anzeige(self) -> None:
        self.label_anzeige.config(text=str(self.logik.wert))

6. 🎯 Deine Aufgaben in aufgabe.py

Öffne die Datei aufgabe.py und implementiere die Klassen Schritt für Schritt:

💡 Lösungshinweise (Aufklappen bei Bedarf)

🔍 Tipp zu TODO 2: Clamping an Grenzwerte (min/max)

Um einen Wert nach oben und unten sauber zu deckeln:

# Erhöhen mit oberer Grenze:
self.wert = min(self.max_wert, self.wert + self.schrittweite)

# Verringern mit unterer Grenze:
self.wert = max(self.min_wert, self.wert - self.schrittweite)
🔍 Tipp zu TODO 5d: Fehler beim Entry-Auslesen abfangen

Benutzer können Tippfehler machen (z.B. Buchstaben eingeben). Fange das mit try-except ab:

eingabe = self.entry_schritt.get().strip()
try:
    neuer_schritt = int(eingabe)
    if self.logik.setze_schrittweite(neuer_schritt):
        self.aktualisiere_anzeige()
    else:
        self.label_info.config(text="⚠️ Schrittweite muss > 0 sein!")
except ValueError:
    self.label_info.config(text="⚠️ Bitte eine ganze Zahl eingeben!")

7. 🧪 Lösung überprüfen & Starten

1. Automatische Tests ausführen:

Terminal (im Ordner 14_gui_mit_tkinter)
python3 test_aufgabe.py

2. Fertige grafische Applikation starten:

Terminal
python3 gui_app.py