FreeSimpleGUI für Anfänger
Python GUIs in wenigen Minuten — einfacher geht's nicht
1 · Was ist FreeSimpleGUI?
FreeSimpleGUI ist ein Python-Paket für die schnelle Erstellung von Desktop-GUIs. Es ist ein freier Fork von PySimpleGUI, das 2023 kommerziell wurde. Mit FreeSimpleGUI kannst du in wenigen Zeilen Code professionell aussehende Fenster bauen.
Blitzschnell
GUI in 5 Zeilen
100% kostenlos
MIT-Lizenz
Mehrere Backends
tkinter, Qt, wx
Wenig Code
Kein OOP nötig
Vergleich mit anderen GUI-Frameworks
| Framework | Einstieg | Flexibilität | Ideal für |
|---|---|---|---|
| FreeSimpleGUI | ⭐⭐⭐⭐⭐ Sehr leicht | ⭐⭐⭐ | Schnelle Tools, Skripte |
| ttkbootstrap | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Schöne Desktop-Apps |
| tkinter | ⭐⭐⭐ | ⭐⭐⭐ | Grundlage / Lernen |
| PyQt6 | ⭐⭐ | ⭐⭐⭐⭐⭐ | Professionelle Apps |
2 · Installation
pip install FreeSimpleGUI
pip3 install FreeSimpleGUIInstallation prüfen
import FreeSimpleGUI as sg print(sg.version) # z.B. 5.1.0
Import-Konvention
FreeSimpleGUI wird fast immer als sg importiert — das ist die Community-Konvention, die du in jedem Tutorial und jeder Dokumentation findest.
import FreeSimpleGUI as sg # Standard ✅ # Alternativ (falls du PySimpleGUI-Code portierst) # import PySimpleGUI as sg # nicht mehr kostenlos # import FreeSimpleGUI as sg # Drop-in-Ersatz!
Backends
Standardmäßig nutzt FreeSimpleGUI tkinter als Backend. Das ist überall vorinstalliert und für Anfänger ideal. Fortgeschrittene können auf Qt oder wx wechseln.
pip install FreeSimpleGUI[Qt] # PyQt5-Backend pip install FreeSimpleGUI[wx] # wxPython-Backend
3 · Erstes Fenster
Das Grundprinzip von FreeSimpleGUI ist immer gleich: Layout definieren → Fenster erstellen → Event-Schleife.
import FreeSimpleGUI as sg
# 1. Layout: Liste von Zeilen, jede Zeile ist eine Liste von Elementen
layout = [
[sg.Text("Hallo, FreeSimpleGUI!")],
[sg.Text("Dein Name:"), sg.Input(key="-NAME-")],
[sg.Button("Grüßen"), sg.Button("Beenden")]
]
# 2. Fenster erstellen
fenster = sg.Window("Mein erstes Fenster", layout)
# 3. Event-Schleife
while True:
event, values = fenster.read()
if event in (sg.WIN_CLOSED, "Beenden"):
break
if event == "Grüßen":
name = values["-NAME-"]
sg.popup(f"Hallo, {name}!")
fenster.close()
•
fenster.read() wartet auf eine Aktion und gibt event (was passierte) und values (alle Eingabewerte) zurück.•
sg.WIN_CLOSED = Benutzer hat das Fenster mit ✕ geschlossen.•
key="-NAME-" = eindeutiger Name, um den Wert auszulesen.
Fenster-Optionen
fenster = sg.Window(
"Titel",
layout,
size=(500, 300), # Breite x Höhe
location=(100, 100), # Position auf dem Bildschirm
resizable=True, # Größe änderbar
finalize=True, # sofort rendern (für Updates)
icon="icon.ico", # Fenster-Icon
keep_on_top=True # immer im Vordergrund
)
4 · Layout-Prinzip
Das Layout ist eine Liste von Zeilen. Jede Zeile ist eine Liste von Elementen. Die Elemente werden von links nach rechts, die Zeilen von oben nach unten angeordnet.
layout = [
# Zeile 1: ein Text
[sg.Text("Überschrift")],
# Zeile 2: Label + Eingabefeld nebeneinander
[sg.Text("Name:"), sg.Input(key="-NAME-")],
# Zeile 3: zwei Buttons nebeneinander
[sg.Button("OK"), sg.Button("Abbrechen")],
]
Spaltenbreite & Ausrichtung steuern
layout = [
# Text rechtsbündig, feste Breite
[sg.Text("Name:", size=(12,1), justification="right"),
sg.Input(size=(25,1), key="-NAME-")],
# Elemente zentrieren mit Push
[sg.Push(), sg.Button("OK"), sg.Button("Abbrechen"), sg.Push()],
# Trennlinie
[sg.HorizontalSeparator()],
# Mehrspaltig mit Column
[
sg.Column([[sg.Text("Links")], [sg.Input()]]),
sg.VSeparator(),
sg.Column([[sg.Text("Rechts")], [sg.Input()]]),
],
]
sg.Push() ist ein unsichtbares Element, das den verfügbaren Platz auffüllt. Zwei Push() um ein Element herum zentrieren es.
Frame (Gruppenrahmen)
frame_layout = [
[sg.Text("Vorname:"), sg.Input(key="-VN-")],
[sg.Text("Nachname:"), sg.Input(key="-NN-")],
]
layout = [
[sg.Frame("Persönliche Daten", frame_layout, font="Any 11")],
[sg.Button("Speichern")],
]
5 · Elemente im Überblick
Text & Eingabe
import FreeSimpleGUI as sg
layout = [
# Einfacher Text
[sg.Text("Normaler Text")],
[sg.Text("Groß & fett", font=("Helvetica", 14, "bold"))],
[sg.Text("Rot", text_color="red")],
# Einzeiliges Eingabefeld
[sg.Text("Name:"), sg.Input(key="-NAME-", size=(20,1))],
[sg.Text("Passwort:"), sg.Input(key="-PW-", password_char="*")],
# Mehrzeiliges Textfeld
[sg.Multiline(default_text="Schreib hier...",
size=(40,5), key="-TEXT-")],
# Nur-Lese-Ausgabe
[sg.Text("", key="-AUSGABE-", size=(30,1))],
]
fenster = sg.Window("Elemente", layout)
while True:
event, values = fenster.read()
if event == sg.WIN_CLOSED: break
fenster.close()
Buttons & Auswahl
layout = [
# Buttons
[sg.Button("Normal"),
sg.Button("Farbe", button_color=("white","#cc2222")),
sg.Button("Deaktiviert", disabled=True)],
# Checkbox
[sg.Checkbox("Newsletter abonnieren", default=True, key="-NL-")],
[sg.Checkbox("AGB akzeptieren", key="-AGB-")],
# Radio-Buttons (gleiche group_id = nur einer wählbar)
[sg.Radio("Option A", "GRUPPE1", default=True, key="-A-")],
[sg.Radio("Option B", "GRUPPE1", key="-B-")],
[sg.Radio("Option C", "GRUPPE1", key="-C-")],
# Dropdown
[sg.Text("Land:"),
sg.Combo(["Österreich","Deutschland","Schweiz"],
default_value="Österreich", key="-LAND-")],
# Schieberegler
[sg.Text("Lautstärke:"),
sg.Slider(range=(0,100), default_value=50,
orientation="h", key="-VOL-")],
[sg.Button("Werte lesen")],
]
Bilder & Fortschritt
layout = [
# Bild anzeigen
[sg.Image("logo.png", key="-BILD-")],
# Fortschrittsbalken
[sg.ProgressBar(100, orientation="h",
size=(30,20), key="-PROG-")],
# Statusleiste unten
[sg.StatusBar("Bereit", key="-STATUS-", size=(40,1))],
[sg.Button("Fortschritt +10")],
]
fenster = sg.Window("Demo", layout)
fortschritt = 0
while True:
event, values = fenster.read()
if event == sg.WIN_CLOSED: break
if event == "Fortschritt +10":
fortschritt = min(100, fortschritt + 10)
fenster["-PROG-"].update(fortschritt)
fenster["-STATUS-"].update(f"Fortschritt: {fortschritt}%")
fenster.close()
6 · Events & Werte
fenster.read() gibt immer zwei Werte zurück: event (was passierte) und values (Dictionary aller aktuellen Eingabewerte).
import FreeSimpleGUI as sg
layout = [
[sg.Text("Name:"), sg.Input(key="-NAME-")],
[sg.Text("Alter:"), sg.Input(key="-ALTER-")],
[sg.Button("Speichern"), sg.Button("Löschen"), sg.Button("Beenden")],
[sg.Text("", key="-AUSGABE-", size=(40,2))],
]
fenster = sg.Window("Events Demo", layout)
while True:
event, values = fenster.read()
# Fenster geschlossen oder Beenden-Button
if event in (sg.WIN_CLOSED, "Beenden"):
break
# Speichern-Button
elif event == "Speichern":
name = values["-NAME-"]
alter = values["-ALTER-"]
fenster["-AUSGABE-"].update(f"Gespeichert: {name}, {alter} Jahre")
# Löschen-Button
elif event == "Löschen":
fenster["-NAME-"].update("") # Feld leeren
fenster["-ALTER-"].update("")
fenster["-AUSGABE-"].update("")
fenster.close()
Elemente zur Laufzeit aktualisieren
# Element über key["-KEY-"].update() ändern:
fenster["-TEXT-"].update("Neuer Text")
fenster["-TEXT-"].update(visible=False) # verstecken
fenster["-BTN-"].update(disabled=True) # deaktivieren
fenster["-INPUT-"].update("Vorbelegung") # Wert setzen
fenster["-COMBO-"].update(values=["A","B","C"]) # Liste ändern
fenster.set_title("Neuer Fenstertitel") # Titel ändern
Timeout — regelmäßige Aktionen
import FreeSimpleGUI as sg
from datetime import datetime
layout = [
[sg.Text("", key="-ZEIT-", font=("Helvetica", 24), justification="center", expand_x=True)],
]
fenster = sg.Window("Uhr", layout, size=(300,100))
while True:
event, values = fenster.read(timeout=1000) # jede Sekunde
if event == sg.WIN_CLOSED:
break
# timeout=1000 → event ist sg.TIMEOUT_EVENT jede Sekunde
jetzt = datetime.now().strftime("%H:%M:%S")
fenster["-ZEIT-"].update(jetzt)
fenster.close()
7 · Themes
FreeSimpleGUI kommt mit über 130 eingebauten Themes. Du setzt das Theme vor dem Erstellen des Fensters.
import FreeSimpleGUI as sg
# Theme setzen (vor Window-Erstellung!)
sg.theme("DarkRed")
layout = [[sg.Text("Ich benutze DarkRed!")], [sg.Button("OK")]]
fenster = sg.Window("Theme Demo", layout)
fenster.read()
fenster.close()
Alle Themes anzeigen
import FreeSimpleGUI as sg # Alle Theme-Namen ausgeben print(sg.theme_list()) # Interaktive Theme-Vorschau öffnen sg.theme_previewer()
Empfehlenswerte dunkle Themes
| Theme-Name | Beschreibung |
|---|---|
DarkRed | Dunkel mit rotem Akzent — passend zu nihonnet |
DarkBlack1 | Fast schwarz, minimalistisch |
DarkGrey14 | Dunkelgrau, professionell |
DarkTeal | Dunkel mit türkisem Akzent |
DarkAmber | Dunkel mit goldenem Akzent |
Black | Reines Schwarz |
Eigenes Theme definieren
import FreeSimpleGUI as sg
# Eigenes Theme (nihonnet-Stil)
sg.theme_add_new("nihonnet", {
"BACKGROUND": "#0a0a1a",
"TEXT": "#b0b8c8",
"INPUT": "#12122a",
"TEXT_INPUT": "#ffffff",
"SCROLL": "#2a2a4a",
"BUTTON": ("#ffffff", "#cc2222"),
"PROGRESS": ("#cc2222", "#1a1a35"),
"BORDER": 1,
"SLIDER_DEPTH": 0,
"PROGRESS_DEPTH": 0,
})
sg.theme("nihonnet")
layout = [[sg.Text("nihonnet-Theme!")], [sg.Button("OK")]]
fenster = sg.Window("Demo", layout)
fenster.read()
fenster.close()
8 · Formulare
Ein vollständiges Eingabeformular — das häufigste Anwendungsszenario für FreeSimpleGUI.
import FreeSimpleGUI as sg
sg.theme("DarkRed")
layout = [
[sg.Text("Registrierung", font=("Helvetica", 14, "bold"),
justification="center", expand_x=True)],
[sg.HorizontalSeparator()],
[sg.Text("Vorname:", size=(12,1)), sg.Input(key="-VN-", size=(25,1))],
[sg.Text("Nachname:", size=(12,1)), sg.Input(key="-NN-", size=(25,1))],
[sg.Text("E-Mail:", size=(12,1)), sg.Input(key="-MAIL-", size=(25,1))],
[sg.Text("Passwort:", size=(12,1)), sg.Input(key="-PW-", size=(25,1), password_char="*")],
[sg.Text("Land:", size=(12,1)),
sg.Combo(["Österreich","Deutschland","Schweiz","Andere"],
default_value="Österreich", key="-LAND-", size=(23,1))],
[sg.Text("Geburtsjahr:", size=(12,1)),
sg.Spin([str(j) for j in range(1930, 2011)],
initial_value="1990", key="-JAHR-")],
[sg.Checkbox("Newsletter abonnieren", key="-NL-")],
[sg.Checkbox("AGB akzeptieren", key="-AGB-")],
[sg.HorizontalSeparator()],
[sg.Push(), sg.Button("Registrieren", size=(14,1)),
sg.Button("Abbrechen", size=(14,1)), sg.Push()],
[sg.Text("", key="-MSG-", text_color="red", expand_x=True, justification="center")],
]
fenster = sg.Window("Registrierung", layout, size=(420, 380))
while True:
event, values = fenster.read()
if event in (sg.WIN_CLOSED, "Abbrechen"):
break
if event == "Registrieren":
# Validierung
if not values["-VN-"] or not values["-NN-"]:
fenster["-MSG-"].update("Bitte Vor- und Nachname eingeben.")
continue
if "@" not in values["-MAIL-"]:
fenster["-MSG-"].update("Ungültige E-Mail-Adresse.")
continue
if len(values["-PW-"]) < 6:
fenster["-MSG-"].update("Passwort mind. 6 Zeichen.")
continue
if not values["-AGB-"]:
fenster["-MSG-"].update("Bitte AGB akzeptieren.")
continue
sg.popup_ok(
f"Willkommen, {values['-VN-']} {values['-NN-']}!\n"
f"Land: {values['-LAND-']}\n"
f"Newsletter: {'Ja' if values['-NL-'] else 'Nein'}",
title="Registrierung erfolgreich"
)
break
fenster.close()
9 · Popups & Dialoge
FreeSimpleGUI hat viele fertige Popup-Funktionen — du brauchst dafür kein eigenes Fenster zu bauen.
import FreeSimpleGUI as sg
sg.theme("DarkRed")
# Einfache Meldung
sg.popup("Das ist eine Nachricht.")
# Mit Titel
sg.popup("Gespeichert!", title="Erfolg")
# OK + Abbrechen
antwort = sg.popup_ok_cancel("Möchtest du fortfahren?")
if antwort == "OK":
print("Weiter!")
# Ja / Nein
antwort = sg.popup_yes_no("Wirklich löschen?")
if antwort == "Yes":
print("Gelöscht!")
# Nur OK (kein Schließen-Button)
sg.popup_no_buttons("Wird geladen...", non_blocking=True)
# Fehlermeldung (roter Hintergrund)
sg.popup_error("Fehler beim Speichern!")
# Eingabe-Popup
name = sg.popup_get_text("Wie heißt du?", "Name eingeben")
print("Name:", name)
# Datei auswählen
pfad = sg.popup_get_file("Datei auswählen",
file_types=(("Textdateien","*.txt"),))
print("Gewählt:", pfad)
# Ordner auswählen
ordner = sg.popup_get_folder("Ordner auswählen")
print("Ordner:", ordner)
Nicht-blockierende Popups
import FreeSimpleGUI as sg
import time
sg.theme("DarkRed")
# Popup öffnen ohne Schleife zu blockieren
sg.popup_quick_message("Wird verarbeitet...", auto_close_duration=2)
# Fortschrittsbalken-Popup
for i in range(101):
sg.one_line_progress_meter("Wird geladen", i, 100, "Fortschritt")
time.sleep(0.02)
10 · Tabellen & Listen
Listbox
import FreeSimpleGUI as sg
sg.theme("DarkRed")
staedte = ["Wien", "Innsbruck", "Salzburg", "Graz", "Linz", "Klagenfurt"]
layout = [
[sg.Text("Stadt auswählen:")],
[sg.Listbox(staedte, size=(25,6), key="-LISTE-",
enable_events=True)],
[sg.Text("", key="-AUSWAHL-")],
[sg.Button("Bestätigen"), sg.Button("Beenden")],
]
fenster = sg.Window("Listbox Demo", layout)
while True:
event, values = fenster.read()
if event in (sg.WIN_CLOSED, "Beenden"): break
if event in ("-LISTE-", "Bestätigen"):
auswahl = values["-LISTE-"]
if auswahl:
fenster["-AUSWAHL-"].update(f"Gewählt: {auswahl[0]}")
fenster.close()
Table (Datentabelle)
import FreeSimpleGUI as sg
sg.theme("DarkRed")
# Tabellendaten
kopfzeile = ["Name", "Alter", "Stadt", "Beruf"]
daten = [
["Tino", 42, "Kirchberg", "Informatiker"],
["Anna", 35, "Innsbruck", "Ärztin"],
["Klaus", 58, "Wien", "Lehrer"],
["Maria", 29, "Salzburg", "Designerin"],
["Peter", 45, "Graz", "Ingenieur"],
]
layout = [
[sg.Text("Personenliste", font=("Helvetica", 12, "bold"))],
[sg.Table(
values=daten,
headings=kopfzeile,
max_col_width=20,
auto_size_columns=True,
display_row_numbers=True,
justification="left",
num_rows=10,
key="-TABELLE-",
enable_events=True,
expand_x=True,
)],
[sg.Text("Gewählt:", key="-INFO-")],
[sg.Button("Beenden")],
]
fenster = sg.Window("Tabelle", layout, size=(550, 300), resizable=True)
while True:
event, values = fenster.read()
if event in (sg.WIN_CLOSED, "Beenden"): break
if event == "-TABELLE-" and values["-TABELLE-"]:
zeile = values["-TABELLE-"][0] # Index der geklickten Zeile
person = daten[zeile]
fenster["-INFO-"].update(f"Gewählt: {person[0]}, {person[2]}")
fenster.close()
11 · Praxisprojekt: Passwort-Manager
Ein vollständiger Passwort-Manager mit Tabelle, Hinzufügen, Löschen und JSON-Speicherung.
import FreeSimpleGUI as sg
import json, os, pyperclip
DATEI = "passwoerter.json"
sg.theme("DarkRed")
def laden():
if os.path.exists(DATEI):
with open(DATEI, "r", encoding="utf-8") as f:
return json.load(f)
return []
def speichern(daten):
with open(DATEI, "w", encoding="utf-8") as f:
json.dump(daten, f, ensure_ascii=False, indent=2)
def tabelle_daten(eintraege):
return [[e["seite"], e["benutzer"], "••••••••"] for e in eintraege]
eintraege = laden()
layout = [
[sg.Text("🔐 Passwort-Manager", font=("Helvetica", 14, "bold"),
expand_x=True, justification="center")],
[sg.HorizontalSeparator()],
# Tabelle
[sg.Table(
values=tabelle_daten(eintraege),
headings=["Website", "Benutzer", "Passwort"],
auto_size_columns=True,
num_rows=10,
key="-TABLE-",
enable_events=True,
expand_x=True,
justification="left",
)],
[sg.HorizontalSeparator()],
# Eingabe
[sg.Text("Website:", size=(10,1)), sg.Input(key="-SITE-", size=(20,1)),
sg.Text("Benutzer:", size=(10,1)), sg.Input(key="-USER-", size=(20,1))],
[sg.Text("Passwort:", size=(10,1)), sg.Input(key="-PASS-", size=(20,1), password_char="*"),
sg.Push(),
sg.Button("➕ Hinzufügen", size=(14,1)),
sg.Button("🗑 Löschen", size=(14,1)),
sg.Button("📋 Kopieren", size=(14,1)),
],
[sg.Text("", key="-MSG-", text_color="#cc2222", expand_x=True)],
]
fenster = sg.Window("Passwort-Manager", layout, size=(680, 400), resizable=True, finalize=True)
ausgewaehlt = None
while True:
event, values = fenster.read()
if event == sg.WIN_CLOSED:
break
elif event == "-TABLE-":
if values["-TABLE-"]:
ausgewaehlt = values["-TABLE-"][0]
e = eintraege[ausgewaehlt]
fenster["-SITE-"].update(e["seite"])
fenster["-USER-"].update(e["benutzer"])
fenster["-PASS-"].update(e["passwort"])
elif event == "➕ Hinzufügen":
site = values["-SITE-"].strip()
user = values["-USER-"].strip()
pw = values["-PASS-"].strip()
if not site or not user or not pw:
fenster["-MSG-"].update("Alle Felder ausfüllen!")
continue
eintraege.append({"seite": site, "benutzer": user, "passwort": pw})
speichern(eintraege)
fenster["-TABLE-"].update(values=tabelle_daten(eintraege))
fenster["-SITE-"].update(""); fenster["-USER-"].update(""); fenster["-PASS-"].update("")
fenster["-MSG-"].update(f"✅ {site} gespeichert.")
elif event == "🗑 Löschen":
if ausgewaehlt is not None:
name = eintraege[ausgewaehlt]["seite"]
if sg.popup_yes_no(f"'{name}' wirklich löschen?") == "Yes":
eintraege.pop(ausgewaehlt)
speichern(eintraege)
fenster["-TABLE-"].update(values=tabelle_daten(eintraege))
ausgewaehlt = None
fenster["-MSG-"].update("🗑 Eintrag gelöscht.")
elif event == "📋 Kopieren":
if ausgewaehlt is not None:
try:
pyperclip.copy(eintraege[ausgewaehlt]["passwort"])
fenster["-MSG-"].update("📋 Passwort in Zwischenablage kopiert.")
except:
fenster["-MSG-"].update("pyperclip nicht installiert: pip install pyperclip")
fenster.close()
pip install pyperclip — für die Kopieren-Funktion. Das restliche Projekt läuft auch ohne es.
🎓 Glückwunsch!
Du kennst jetzt alle Grundlagen von FreeSimpleGUI.