it-praxisportal/14_gui_mit_tkinter/aufgabe.py

303 lines
12 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.

"""
Kapitel 14: Desktop-GUIs mit Tkinter & OOP 🖥️🔢
=================================================
Aufgabe: Erstelle eine modulare Klick-Zähler- & Rechner-Applikation
mit sauberer Trennung von Logik (Model) und Benutzeroberfläche (View/Controller).
1. ZaehlerLogik: Verwaltet den Wert, Grenzwerte und Schrittweiten (Headless / Reines Python).
2. ZaehlerApp: Erzeugt das Tkinter-Fenster, platziert Buttons & Labels und fängt Klicks ab.
"""
from typing import Optional
# ==============================================================================
# Tkinter Import mit Fallback für Headless- / Server-Umgebungen
# ==============================================================================
try:
import tkinter as tk
except ImportError:
# Headless Mock-Objekte (ermöglicht Ausführung und Tests auch ohne GUI-Server)
class _MockWidget:
def __init__(self, master=None, text="", **kwargs):
self.master = master
self._config = dict(kwargs)
if text:
self._config["text"] = text
self._entry_text = str(kwargs.get("textvariable", text))
def config(self, **kwargs):
self._config.update(kwargs)
def configure(self, **kwargs):
self.config(**kwargs)
def cget(self, key):
return self._config.get(key, "")
def __getitem__(self, key):
return self._config.get(key, "")
def __setitem__(self, key, value):
self._config[key] = value
def grid(self, **kwargs):
pass
def pack(self, **kwargs):
pass
def get(self):
return self._entry_text
def delete(self, first=0, last=None):
self._entry_text = ""
def insert(self, index, string):
self._entry_text = str(string)
def invoke(self):
cmd = self._config.get("command")
if callable(cmd):
return cmd()
class _MockTk(_MockWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._title = ""
self._geometry = ""
def title(self, title: str):
self._title = title
def geometry(self, geom: str):
self._geometry = geom
def mainloop(self):
pass
def destroy(self):
pass
class _MockTkModule:
Tk = _MockTk
Label = _MockWidget
Button = _MockWidget
Entry = _MockWidget
Frame = _MockWidget
tk = _MockTkModule()
# ==============================================================================
# TEIL 1: DIE LOGIK-KLASSE (MODEL)
# ==============================================================================
class ZaehlerLogik:
"""
Das Datenmodell (Model) des Klick-Zählers.
Verwaltet den aktuellen Zählerstand, Grenzwerte und Schrittweiten.
Komplett unabhängig von Tkinter (reines Python).
"""
# ==========================================================================
# TODO 1: Konstruktor __init__
# Parameter:
# - startwert (int, Standard: 0)
# - min_wert (int, Standard: -100)
# - max_wert (int, Standard: 100)
# Attribute initialisieren:
# - 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 __init__(self, startwert: int = 0, min_wert: int = -100, max_wert: int = 100):
pass
# ==========================================================================
# TODO 2a: Methode erhoehen(self) -> int
# 1. Erhöhe self.klick_anzahl um 1.
# 2. Addiere self.schrittweite zu self.wert, aber maximal bis self.max_wert!
# Tipp: min(self.max_wert, self.wert + self.schrittweite)
# 3. Gib den neuen self.wert zurück.
# ==========================================================================
def erhoehen(self) -> int:
pass
# ==========================================================================
# TODO 2b: Methode verringern(self) -> int
# 1. Erhöhe self.klick_anzahl um 1.
# 2. Subtrahiere self.schrittweite von self.wert, aber minimal bis self.min_wert!
# Tipp: max(self.min_wert, self.wert - self.schrittweite)
# 3. Gib den neuen self.wert zurück.
# ==========================================================================
def verringern(self) -> int:
pass
# ==========================================================================
# TODO 2c: Methode zuruecksetzen(self) -> int
# 1. Erhöhe self.klick_anzahl um 1.
# 2. Setze self.wert auf 0 zurück.
# 3. Gib den neuen self.wert (0) zurück.
# ==========================================================================
def zuruecksetzen(self) -> int:
pass
# ==========================================================================
# TODO 3a: Methode setze_schrittweite(self, schritt: int) -> bool
# Wenn schritt > 0 ist:
# - Setze self.schrittweite = schritt
# - Gib True zurück
# Wenn schritt <= 0 ist:
# - Verändere die Schrittweite nicht und gib False zurück
# ==========================================================================
def setze_schrittweite(self, schritt: int) -> bool:
pass
# ==========================================================================
# TODO 3b: Methode ist_gerade(self) -> bool
# Gibt True zurück, wenn self.wert eine gerade Zahl ist (wert % 2 == 0),
# andernfalls False.
# ==========================================================================
def ist_gerade(self) -> bool:
pass
# ==========================================================================
# TODO 3c: Dunder-Methode __str__(self) -> str
# Gibt einen formatierten String zurück:
# "Zählerstand: <wert> (Schrittweite: <schrittweite>, Klicks: <klick_anzahl>)"
# ==========================================================================
def __str__(self) -> str:
pass
# ==============================================================================
# TEIL 2: DIE GUI-KLASSE (VIEW & CONTROLLER)
# ==============================================================================
class ZaehlerApp:
"""
Die Benutzeroberfläche (GUI) für den Klick-Zähler mit Tkinter.
Erstellt Fenster, Labels, Buttons und Eingabefelder.
"""
# ==========================================================================
# TODO 4: Konstruktor __init__(self, root: tk.Tk)
# Parameter: root (das Tkinter-Hauptfenster)
#
# 1. Speichere das Fenster:
# self.root = root
# self.root.title("Python OOP Zähler 🔢")
# self.root.geometry("380x360")
#
# 2. Erstelle eine Instanz der Logik-Klasse:
# self.logik = ZaehlerLogik(startwert=0, min_wert=-100, max_wert=100)
#
# 3. Erstelle und platziere alle Widgets:
# - self.label_titel: tk.Label(self.root, text="🔢 Python Klick-Zähler", font=("Arial", 16, "bold"))
# Platzierung mit .grid(row=0, column=0, columnspan=2, padx=15, pady=(15, 5))
#
# - self.label_anzeige: tk.Label(self.root, text="0", font=("Arial", 36, "bold"))
# Platzierung mit .grid(row=1, column=0, columnspan=2, padx=15, pady=10)
#
# - self.label_info: tk.Label(self.root, text="Klicks: 0 | Gerade Zahl", font=("Arial", 10))
# Platzierung mit .grid(row=2, column=0, columnspan=2, padx=15, pady=(0, 15))
#
# - self.btn_plus: tk.Button(self.root, text="➕ Erhöhen", font=("Arial", 11, "bold"), command=self.klick_plus)
# Platzierung mit .grid(row=3, column=0, padx=10, pady=5, sticky="ew")
#
# - self.btn_minus: tk.Button(self.root, text="➖ Verringern", font=("Arial", 11, "bold"), command=self.klick_minus)
# Platzierung mit .grid(row=3, column=1, padx=10, pady=5, sticky="ew")
#
# - self.btn_reset: tk.Button(self.root, text="↺ Zurücksetzen", font=("Arial", 10), command=self.klick_reset)
# Platzierung mit .grid(row=4, column=0, columnspan=2, padx=10, pady=5, sticky="ew")
#
# - Container für Schrittweite:
# self.frame_schritt = tk.Frame(self.root)
# self.frame_schritt.grid(row=5, column=0, columnspan=2, padx=10, pady=(15, 10))
#
# self.label_schritt = tk.Label(self.frame_schritt, text="Schrittweite:")
# self.label_schritt.pack(side="left", padx=5)
#
# self.entry_schritt = tk.Entry(self.frame_schritt, width=6, font=("Arial", 10))
# self.entry_schritt.insert(0, "1")
# self.entry_schritt.pack(side="left", padx=5)
#
# self.btn_schritt = tk.Button(self.frame_schritt, text="Setzen", command=self.klick_schritt_setzen)
# self.btn_schritt.pack(side="left", padx=5)
#
# 4. Rufe am Ende self.aktualisiere_anzeige() auf, um die Startwerte anzuzeigen!
# ==========================================================================
def __init__(self, root: tk.Tk):
pass
# ==========================================================================
# TODO 5a: Methode klick_plus(self) -> None
# 1. Rufe self.logik.erhoehen() auf.
# 2. Rufe self.aktualisiere_anzeige() auf.
# ==========================================================================
def klick_plus(self) -> None:
pass
# ==========================================================================
# TODO 5b: Methode klick_minus(self) -> None
# 1. Rufe self.logik.verringern() auf.
# 2. Rufe self.aktualisiere_anzeige() auf.
# ==========================================================================
def klick_minus(self) -> None:
pass
# ==========================================================================
# TODO 5c: Methode klick_reset(self) -> None
# 1. Rufe self.logik.zuruecksetzen() auf.
# 2. Rufe self.aktualisiere_anzeige() auf.
# ==========================================================================
def klick_reset(self) -> None:
pass
# ==========================================================================
# TODO 5d: Methode klick_schritt_setzen(self) -> None
# 1. Hole den Text aus self.entry_schritt mit self.entry_schritt.get().strip()
# 2. Versuche (try-except) den Text mit int() in eine Ganzzahl umzuwandeln.
# 3. Wenn die Umwandlung klappt:
# - Rufe self.logik.setze_schrittweite(neuer_wert) auf.
# - Wenn setze_schrittweite True liefert: self.aktualisiere_anzeige() aufrufen.
# - Wenn False geliefert wird: self.label_info.config(text="⚠️ Schrittweite muss > 0 sein!")
# 4. Wenn ein ValueError auftritt (z.B. Buchstabe eingegeben):
# - Setze self.label_info.config(text="⚠️ Bitte eine ganze Zahl eingeben!")
# ==========================================================================
def klick_schritt_setzen(self) -> None:
pass
# ==========================================================================
# TODO 6: Methode aktualisiere_anzeige(self) -> None
# Synchronisiert die Oberfläche mit den aktuellen Werten aus self.logik:
#
# 1. Hauptanzeige:
# self.label_anzeige.config(text=str(self.logik.wert))
#
# 2. Info-Text zusammenbauen:
# 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 += " (Maximum erreicht!)"
# elif self.logik.wert <= self.logik.min_wert:
# info += " (Minimum erreicht!)"
# self.label_info.config(text=info)
# ==========================================================================
def aktualisiere_anzeige(self) -> None:
pass
# ==============================================================================
# Startblock: Führe dieses Skript aus (python3 aufgabe.py), um die GUI zu testen!
# ==============================================================================
if __name__ == "__main__":
print("🚀 Starte Tkinter Klick-Zähler...")
try:
root = tk.Tk()
app = ZaehlerApp(root)
print("💡 Fenster geöffnet! Schließe das Fenster, um das Programm zu beenden.")
root.mainloop()
except Exception as e:
print(f"⚠️ GUI konnte nicht im Display geöffnet werden ({e}).")
print("💡 Führe 'python3 test_aufgabe.py' aus, um deine Lösung automatisch zu prüfen!")