FreeSimpleGUI Logo

FreeSimpleGUI für Anfänger

Python GUIs in wenigen Minuten — einfacher geht's nicht

11 Kapitel Python 3.6+ Kostenlos & Open Source Viele Beispiele Offline verfügbar

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.

ℹ️ Hintergrund: PySimpleGUI war jahrelang kostenlos und beliebt. Nach der Umstellung auf ein Abo-Modell entstand FreeSimpleGUI als 100% kostenlose Alternative — mit identischer API.

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
FrameworkEinstiegFlexibilitätIdeal für
FreeSimpleGUI⭐⭐⭐⭐⭐ Sehr leicht⭐⭐⭐Schnelle Tools, Skripte
ttkbootstrap⭐⭐⭐⭐⭐⭐⭐⭐Schöne Desktop-Apps
tkinter⭐⭐⭐⭐⭐⭐Grundlage / Lernen
PyQt6⭐⭐⭐⭐⭐⭐⭐Professionelle Apps

2 · Installation

Terminal
pip install FreeSimpleGUI
💡 Auf manchen Systemen: pip3 install FreeSimpleGUI
Installation prüfen
Python
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.

Python
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.

Terminal — optionale Backends
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.

hallo.py
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()
💡 Wie es funktioniert:
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
Python
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.

Aufbau
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
Python
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)
Python
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
Python
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
Python
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
Python
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).

Python — Events verstehen
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
Python
# 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
Python — Uhr-Beispiel
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.

Python — Theme setzen
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
Python
import FreeSimpleGUI as sg

# Alle Theme-Namen ausgeben
print(sg.theme_list())

# Interaktive Theme-Vorschau öffnen
sg.theme_previewer()
Empfehlenswerte dunkle Themes
Theme-NameBeschreibung
DarkRedDunkel mit rotem Akzent — passend zu nihonnet
DarkBlack1Fast schwarz, minimalistisch
DarkGrey14Dunkelgrau, professionell
DarkTealDunkel mit türkisem Akzent
DarkAmberDunkel mit goldenem Akzent
BlackReines Schwarz
Eigenes Theme definieren
Python
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.

formular.py
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.

Python — Alle Popup-Typen
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
Python
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
Python
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)
Python
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.

passwort_manager.py
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()
💡 pyperclip installieren: 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.

Installation ✓ Layout ✓ Elemente ✓ Events ✓ Themes ✓ Formulare ✓ Popups ✓ Tabellen ✓ Projekt ✓
GitHub Docs