14. GUI mit Python
GUI mit Python
Abschnitt betitelt „GUI mit Python“Konsolenprogramme sind für Entwickler; Endbenutzer erwarten eine grafische Benutzeroberfläche (GUI, Graphical User Interface) mit Fenstern, Eingabefeldern und Schaltflächen. Dieses Kapitel führt in die GUI-Entwicklung mit Tkinter ein, dem GUI-Modul der Python-Standardbibliothek. Tkinter ist nicht das modernste Toolkit, aber es ist ohne Installation überall verfügbar, und die Konzepte, um die es hier geht, sind toolkitübergreifend gültig: ereignisgesteuerte Programmierung, Widgets, Layout und die Trennung von Oberfläche und Fachlogik. Wer sie mit Tkinter beherrscht, findet sich in Qt, GTK oder einem Web-Frontend schnell zurecht.
Ein anderes Programmiermodell: Ereignissteuerung
Abschnitt betitelt „Ein anderes Programmiermodell: Ereignissteuerung“Konsolenprogramme bestimmen den Ablauf selbst: Sie fragen nach Eingaben, rechnen, geben aus, in genau dieser Reihenfolge. Bei einer GUI kehrt sich die Kontrolle um. Das Programm baut die Oberfläche auf und übergibt dann an eine Ereignisschleife (event loop), die auf Benutzeraktionen wartet: Klicks, Tastendrücke, Fensteränderungen. Für jedes Ereignis, auf das das Programm reagieren soll, registriert es vorab eine Funktion (Event-Handler oder Callback), die die Ereignisschleife im passenden Moment aufruft.
Eigener Code läuft also nur noch in kurzen Reaktionen auf Ereignisse; dazwischen wartet das Programm. Dieses Modell heißt ereignisgesteuerte Programmierung, und es ist das Modell praktisch jeder interaktiven Software, von der Desktop-Anwendung über Apps bis zur Webseite.
Das erste Fenster
Abschnitt betitelt „Das erste Fenster“import tkinter as tk
root = tk.Tk() # the main windowroot.title("First window")root.geometry("300x120")
label = tk.Label(root, text="Hello, GUI!")label.pack(pady=20)
root.mainloop() # hand control to the event loopDrei Dinge sind hier grundsätzlich: tk.Tk() erzeugt das Hauptfenster. Jedes Widget (hier ein Label) bekommt bei der Erzeugung sein Elternelement als erstes Argument und wird mit einem Layout-Aufruf (hier pack) im Fenster platziert. root.mainloop() startet die Ereignisschleife; diese Zeile kehrt erst beim Schließen des Fensters zurück, dahinter stehender Code läuft also nicht “nebenbei”.
Widgets und Ereignisse
Abschnitt betitelt „Widgets und Ereignisse“Die Grundausstattung an Widgets deckt die meisten Formulare ab:
| Widget | Zweck |
|---|---|
Label | Textanzeige |
Button | Schaltfläche mit Klick-Handler |
Entry | einzeiliges Eingabefeld |
Listbox | Auswahlliste |
Text | mehrzeiliges Textfeld |
Frame | unsichtbarer Container zum Gruppieren |
Die Verbindung zwischen Widget und Code stellt der command-Parameter her: Er erhält die Funktion, die beim Klick laufen soll. Ein minimales interaktives Beispiel:
import tkinter as tk
def on_greet(): name = name_entry.get() greeting_label.config(text=f"Hello, {name}!")
root = tk.Tk()root.title("Greeting")
name_entry = tk.Entry(root)name_entry.pack(padx=10, pady=5)
greet_button = tk.Button(root, text="Greet", command=on_greet)greet_button.pack(pady=5)
greeting_label = tk.Label(root, text="")greeting_label.pack(pady=5)
root.mainloop()Zwei Details mit hoher Stolperquote:
command=on_greetübergibt die Funktion selbst, ohne Klammern.command=on_greet()würde die Funktion sofort beim Aufbau aufrufen und ihr Ergebnis (None) als Handler registrieren; der Knopf bliebe wirkungslos.- Widget-Inhalte werden über Methoden gelesen und gesetzt:
entry.get()liefert den aktuellen Text,label.config(text=...)ändert die Anzeige.
Für Ereignisse jenseits von command (Tastatur, Maus, Fokus) gibt es bind: name_entry.bind("<Return>", lambda event: on_greet()) löst die Begrüßung auch mit der Eingabetaste aus.
Layout mit grid
Abschnitt betitelt „Layout mit grid“pack stapelt Widgets der Reihe nach und reicht für einfache Fälle. Für Formulare ist grid das passende Werkzeug: Es ordnet Widgets in einem Raster aus Zeilen und Spalten an.
tk.Label(root, text="Filename:").grid(row=0, column=0, sticky="e", padx=5, pady=5)tk.Entry(root).grid(row=0, column=1, sticky="we", padx=5, pady=5)
tk.Label(root, text="Duration (s):").grid(row=1, column=0, sticky="e", padx=5, pady=5)tk.Entry(root).grid(row=1, column=1, sticky="we", padx=5, pady=5)
root.columnconfigure(1, weight=1) # column 1 grows with the windowsticky richtet ein Widget innerhalb seiner Zelle aus (Himmelsrichtungen: "e" rechts, "we" volle Breite), padx/pady schaffen Abstand, und columnconfigure(..., weight=1) bestimmt, welche Spalte zusätzlichen Platz erhält, wenn das Fenster größer gezogen wird. Wichtig: Innerhalb desselben Containers dürfen pack und grid nicht gemischt werden; pro Frame entscheidet man sich für eines.
Für ein Publikum, das sich mit Gestaltung beschäftigt, sei angemerkt: Auch für GUIs gelten Gestaltungsgrundsätze. Konsistente Abstände, Ausrichtung an einem Raster und klare Beschriftungen sind keine Kosmetik, sondern entscheiden über die Benutzbarkeit. Die Werkzeuge dafür sind genau grid, sticky und padx/pady.
Architektur: Fachlogik und Oberfläche trennen
Abschnitt betitelt „Architektur: Fachlogik und Oberfläche trennen“Der wichtigste Abschnitt dieses Kapitels. Der naheliegende Stil, alle Berechnungen direkt in die Event-Handler zu schreiben, führt zu Programmen, die untestbar und unwartbar sind: Die Logik ist über Dutzende Handler verstreut und nur noch per Mausklick erreichbar. Die Regel dagegen lautet:
Die Fachlogik weiß nichts von der GUI. Die GUI rechnet nicht selbst.
Konkret: Die Fachlogik (Klassen und Funktionen, etwa frames_to_timecode oder die Mediendatenbank aus den früheren Kapiteln) steht in eigenen Modulen ohne jeden tkinter-Import. Die GUI ist eine Schicht darüber: Sie liest Eingaben aus Widgets, ruft die Fachlogik auf und zeigt Ergebnisse an. Ein vollständiges Beispiel nach diesem Muster, aufgebaut als Klasse:
# timecode.py: pure logic, no tkinter, fully testable with pytestdef frames_to_timecode(frames, fps=25): if frames < 0: raise ValueError(f"frame number must be >= 0, got {frames}") total_seconds = frames // fps return (f"{total_seconds // 3600:02d}:{(total_seconds % 3600) // 60:02d}:" f"{total_seconds % 60:02d}:{frames % fps:02d}")# app.py: the GUI layerimport tkinter as tkfrom tkinter import messagebox
from timecode import frames_to_timecode
class TimecodeApp: def __init__(self, root): self.root = root root.title("Timecode converter")
tk.Label(root, text="Frame number:").grid(row=0, column=0, sticky="e", padx=5, pady=5) self.frames_entry = tk.Entry(root) self.frames_entry.grid(row=0, column=1, sticky="we", padx=5, pady=5)
tk.Button(root, text="Convert", command=self.on_convert).grid( row=1, column=0, columnspan=2, pady=5)
self.result_label = tk.Label(root, text="") self.result_label.grid(row=2, column=0, columnspan=2, pady=5)
root.columnconfigure(1, weight=1)
def on_convert(self): try: frames = int(self.frames_entry.get()) timecode = frames_to_timecode(frames) except ValueError: messagebox.showerror( "Invalid input", "Please enter a whole number of frames (0 or greater).") return self.result_label.config(text=timecode)
if __name__ == "__main__": root = tk.Tk() TimecodeApp(root) root.mainloop()Die Klassenform (TimecodeApp) ersetzt die globalen Variablen des Begrüßungsbeispiels: Alle Widgets hängen als Attribute am Objekt, jeder Handler ist eine Methode mit Zugriff über self. Ab mehr als einer Handvoll Widgets ist diese Struktur der Standard.
Die Gewinne der Trennung sind konkret: timecode.py ist mit pytest testbar (Kapitel 11), ohne dass ein Fenster aufgeht; die parametrisierten Tests von damals gelten unverändert weiter. Dieselbe Fachlogik kann von einer Konsolenversion, der GUI und den Tests gemeinsam genutzt werden. Fehlerbehebungen in der Logik berühren die GUI-Datei nicht, und umgekehrt.
Das Muster dahinter: Model-View-Controller
Abschnitt betitelt „Das Muster dahinter: Model-View-Controller“Diese Trennung ist kein Einzelfall, sondern der Kern eines der bekanntesten Architekturmuster überhaupt: Model-View-Controller (MVC). Es zerlegt ein interaktives Programm in drei Verantwortlichkeiten, die sich im Beispiel oben direkt wiederfinden:
- Model — die Daten und die Fachlogik; weiß nichts von der Oberfläche. Hier:
timecode.pymitframes_to_timecode(oder die MediendatenbankMediaAssetaus den Kapiteln 5 und 6). Ohne Fenster testbar. - View — die Darstellung: die Widgets, die den Zustand anzeigen und Eingaben entgegennehmen. Weiß, wie etwas aussieht, nicht, was es bedeutet. Hier:
Label,Entry,Button. - Controller — der Vermittler: reagiert auf Ereignisse, liest die Eingabe aus der View, ruft das Model auf und schreibt das Ergebnis zurück in die View. Hier: der Handler
on_convert.
Entscheidend ist die Abhängigkeitsrichtung: View und Controller kennen das Model, aber das Model kennt weder View noch Controller. Deshalb steht in timecode.py kein import tkinter und kann dort auch nie stehen, die Fachlogik kann nicht “nach oben” in die Oberfläche greifen.
flowchart LR
User(("Benutzer")) -->|"Eingabe / Klick"| Controller
Controller["Controller<br/>(Event-Handler)"] -->|"ruft auf"| Model["Model<br/>(Fachlogik, kein tkinter)"]
Model -->|"Ergebnis / Exception"| Controller
Controller -->|"aktualisiert"| View["View<br/>(Widgets)"]
View -->|"zeigt an"| User
In der reinen Lehre sind View und Controller getrennte Objekte. In kleinen tkinter-Programmen fallen sie oft in einer Klasse zusammen, TimecodeApp baut die Widgets (View) und enthält on_convert (Controller). Das ist bewusst pragmatisch und für überschaubare Oberflächen üblich. Unverhandelbar ist nur die harte Grenze zum Model: Es weiß garantiert nichts von der GUI. Wächst die Anwendung, lohnt es, auch die Controller-Logik in eigene Methoden oder Klassen zu ziehen und die View schlank zu halten.
Der Gewinn ist derselbe wie oben, jetzt mit Namen: Das Model ist isoliert testbar, und dieselbe Model-Schicht kann verschiedene Views tragen, eine Konsolenausgabe, die tkinter-GUI oder eine Weboberfläche. Genau darin liegt die Verbindung zum Ausblick unten: Web-Frameworks wie Flask und Django setzen dasselbe Muster über die Netzwerkgrenze um (Django nennt seine Variante MVT, Model-View-Template).
Eingabefehler abfangen
Abschnitt betitelt „Eingabefehler abfangen“Der Lehrplan nennt das Abfangen von Eingabefehlern ausdrücklich, und GUIs sind der Ort, an dem es zählt: Jedes Entry liefert beliebigen Text, und der Benutzer erwartet bei Fehlern keine Tracebacks, sondern eine verständliche Rückmeldung. Das Muster steht bereits im Beispiel oben und besteht aus drei Teilen:
- Validierung an der Grenze: Der Handler wandelt die Eingabe um (
int(...)) und ruft die Fachlogik auf; beide melden Probleme per Exception (Kapitel 7). - Behandlung im Handler: Der Handler ist die äußere Ebene mit Kontext (Kapitel 7); dort wird gefangen. Ein unbehandelter Fehler in einem Handler stürzt nicht das Programm ab, sondern verpufft mit einem Traceback auf der Konsole, die der Benutzer nie sieht; das Programm wirkt dann schlicht kaputt, ohne Meldung.
- Rückmeldung im Dialog:
tkinter.messageboxliefert Standarddialoge:showerror,showwarning,showinfosowieaskyesnofür Rückfragen (“Wirklich löschen?”). Die Meldung nennt, was falsch war und was erwartet wird, in der Sprache des Benutzers, nicht des Programms.
Größere Oberflächen strukturieren
Abschnitt betitelt „Größere Oberflächen strukturieren“Reale Anwendungen bestehen nicht aus fünf Widgets, sondern aus fünfzig. Zwei Mittel halten das beherrschbar:
- Eigene Widget-Klassen. Zusammengehörige Widget-Gruppen werden als Subklasse von
tk.Framegekapselt, mit eigener Schnittstelle. Eine KlasseAssetListPanel(tk.Frame)etwa enthält Listbox, Scrollbalken und Filterfeld und bietet nach außenget_selected()undrefresh(assets)an. Das Hauptfenster setzt sich dann aus wenigen solchen Bausteinen zusammen, statt hunderte Einzelwidgets zu verwalten. Das ist Objektorientierung (Kapitel 4 bis 6), angewendet auf die Oberfläche; Toolkits sind selbst durchgängig so gebaut (jedes Widget ist eine Klasse,AssetListPanelerbt vonFrame). - Weitere Fenster. Zusätzliche Fenster (Detailansichten, Einstellungsdialoge) erzeugt
tk.Toplevel; sie funktionieren wie das Hauptfenster und werden sinnvollerweise ebenfalls als eigene Klassen umgesetzt.
Ausblick: GUI-Entwicklung im professionellen Umfeld
Abschnitt betitelt „Ausblick: GUI-Entwicklung im professionellen Umfeld“tkinter ist ideal zum Lernen und für kleine Werkzeuge: Es ist ohne Zusatzinstallation dabei, klein und schnell erklärt. Für große, professionell ausgelieferte Programme greift man aber meist zu anderen Werkzeugen. Ein Überblick über die Landschaft.
Desktop: Qt (PyQt und PySide)
Abschnitt betitelt „Desktop: Qt (PyQt und PySide)“Der De-facto-Standard für umfangreiche Python-Desktopanwendungen ist Qt, ein ursprünglich in C++ geschriebenes Toolkit, das über zwei Bindings aus Python nutzbar ist: PyQt (von der Firma Riverbank) und PySide, offiziell “Qt for Python” vom Qt-Hersteller selbst. Beide bieten weit mehr Widgets als tkinter, einen grafischen Oberflächen-Editor (Qt Designer), ein einheitliches Erscheinungsbild über Betriebssysteme hinweg und ausgereifte Werkzeuge für große Projekte.
Prominente, real ausgelieferte Programme in Python und Qt: Calibre (E-Book-Verwaltung), Anki (Karteikarten-Lernprogramm), Spyder (eine Python-Entwicklungsumgebung) und OpenShot (Videoschnitt). Auch der Dropbox-Desktop-Client war lange in Python geschrieben.
Weitere Desktop-Toolkits
Abschnitt betitelt „Weitere Desktop-Toolkits“- Kivy für berührungs- und mobiltaugliche Oberflächen (auch Android und iOS),
- wxPython, das die nativen Widgets des jeweiligen Betriebssystems verwendet,
- modernere Ansätze wie Flet (Oberflächen im Flutter-Stil) oder Toga (aus dem BeeWare-Projekt).
Der häufigste Fall: die Oberfläche ist der Browser
Abschnitt betitelt „Der häufigste Fall: die Oberfläche ist der Browser“Im Berufsalltag ist die verbreitetste “GUI” gar kein Desktopfenster, sondern eine Webseite. Die Oberfläche besteht aus HTML und CSS und läuft im Browser; das Python-Programm erzeugt diese Seiten auf dem Server. Die beiden bekanntesten Werkzeuge dafür sind Flask, ein schlankes Framework, das HTML aus Vorlagen (Jinja2) zusammensetzt, und Django, ein umfassendes Framework mit eigenem Vorlagensystem, Datenbankanbindung und Administrationsoberfläche.
Dass dieser Weg trägt, zeigen die Größenordnungen: Das Backend von Instagram ist eine der größten Django-Anwendungen der Welt, und auch Reddit ist in Python geschrieben. Für datengetriebene Oberflächen ohne eigenes Frontend gibt es zudem Streamlit und Dash, die aus reinem Python-Code fertige Web-Oberflächen erzeugen. Die Trennung dahinter — Fachlogik in Python, Oberfläche als HTML/CSS — ist dieselbe, die dieses Kapitel schon innerhalb von tkinter geübt hat, nur über die Netzwerkgrenze hinweg. Die Oberflächenprogrammierung mit HTML, CSS und JavaScript ist Gegenstand der folgenden Ausbildungsjahre.
Und Godot, Blender und Co.?
Abschnitt betitelt „Und Godot, Blender und Co.?“Häufig genannt, aber ein Sonderfall sind Spiele-Engines. Die Engine Godot etwa nutzt GDScript, eine eigene Sprache, deren Syntax an Python angelehnt ist, Python selbst ist es aber nicht. Blender wiederum ist in C und C++ geschrieben und bietet Python nur als Skript- und Erweiterungssprache. Wo Python tatsächlich die tragende Sprache für Oberfläche und Ablauf ist, sind es Projekte wie Ren’Py (eine Engine für Visual Novels), Pygame oder Panda3D.
Lernergebnisse: Was Sie nach diesem Kapitel können sollten
Abschnitt betitelt „Lernergebnisse: Was Sie nach diesem Kapitel können sollten“Nach Abschluss dieses Kapitels sollten Schülerinnen und Schüler in der Lage sein:
- Erklären: das ereignisgesteuerte Programmiermodell (Ereignisschleife, Handler) und den Unterschied zum ablaufgesteuerten Konsolenprogramm erklären.
- Anwenden: Oberflächen mit den Grundwidgets (Label, Button, Entry, Listbox, Frame) aufbauen und Ereignisse über
commandundbindbehandeln. - Anwenden: Formulare mit
grid,stickyund Gewichtungen sauber und größenveränderlich anordnen. - Entwerfen: GUI-Anwendungen als Klassen strukturieren und die Fachlogik in eigenen, GUI-freien und damit testbaren Modulen halten.
- Erklären: das Muster Model-View-Controller erklären und die drei Rollen (Model, View, Controller) einer tkinter-Anwendung zuordnen.
- Anwenden: Eingabefehler validieren, in Handlern fangen und mit
messageboxbenutzergerecht melden. - Entwerfen: größere Oberflächen mit eigenen Widget-Klassen und zusätzlichen Fenstern strukturieren.
Passende Übungen
Abschnitt betitelt „Passende Übungen“- Aufgabe 22 - To-do-Liste als Desktop-App
- Aufgabe 23 - Vokabeltrainer (Mini-Projekt)