From 5babfc3e2ecd19918e63c0ac0f812097dd2299ff Mon Sep 17 00:00:00 2001 From: TheMockTv Date: Mon, 7 Sep 2026 18:10:40 +0200 Subject: [PATCH] Zwei echte Fehler aus der Pruefkette behoben MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 1. Der Darstellungsdialog zerstoerte sich beim ersten Klick selbst _oberflaeche_neu() loeschte ALLE Kinder des Hauptfensters - und ein tk.Toplevel IST ein Kind. Der offene Dialog wurde also mitgeloescht, die Zeile danach fasste eine Leiche an: _tkinter.TclError: invalid command name ".!toplevel" Die Farbe wechselte zwar und nichts ging verloren, aber das Fenster verschwand beim ersten Klick, jede weitere Umstellung brauchte einen neuen Menueaufruf, und jedes Mal landete ein Traceback im Log. In BEIDEN Programmen. Behoben: fremde Toplevels bleiben stehen. Damit ueberlebt auch ein offener Ordnerwahl- oder Einstellungsdialog den Wechsel. ⚠️ Warum mein Pruefstand es nicht fand: er setzte cfg["darstellung"] von Hand und rief _oberflaeche_neu() direkt - also alles ausser dem Weg, den ein Mensch geht. Den Dialog oeffnete er zwar, mass aber nur dessen Position. pruef_darstellung drueckt jetzt den Radioknopf (.invoke()) und prueft, dass der Dialog danach noch lebt UND dass ein ZWEITER Klick im selben Dialog wirkt - das war vorher unmoeglich. ## 2. Unerreichbarer Code nach einem return _fenstersymbol() hatte zwei Zeilen der alten Inline-Fassung hinter dem return stehen. Rest meines symbol_setzen-Umbaus; per AST ueber alle Dateien beider Ordner belegt (genau ein Treffer). Beides gefunden von der Pruefkette nach R23. Die Aufraeumbefunde derselben Runde (doppelter Code, tote Variablen, 34x dasselbe pruefe()) kommen getrennt - eine Fehlerbehebung soll fuer sich pruefbar bleiben. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax --- app.py | 15 ++++++++--- pruef_darstellung.py | 60 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/app.py b/app.py index 32fe38f..a921b85 100644 --- a/app.py +++ b/app.py @@ -512,8 +512,19 @@ class RechnungsApp(ThemeMixin, MeldungMixin, KorrekturMixin, EinstellungenMixin, except Exception: # noqa: BLE001 - dann eben das erste Blatt pass + # Fremde Fenster stehen lassen! Ein tk.Toplevel IST ein Kind des + # Hauptfensters - der offene Darstellungsdialog wurde hier bisher + # mitgeloescht, und die Zeile danach fasste eine Leiche an + # ("invalid command name .!toplevel"). Gefunden am 07.09.2026 von der + # Pruefkette, mit dem echten Bedienweg gemessen: Klick auf "Dunkel" + # -> Farbe wechselt, Fenster ist weg, Traceback im Log. + # + # Der Pruefstand hat es nicht gefunden, weil er cfg["darstellung"] von + # Hand setzte und _oberflaeche_neu() direkt rief - also alles ausser + # dem Weg, den ein Mensch geht. for kind in self.winfo_children(): - kind.destroy() + if not isinstance(kind, tk.Toplevel): + kind.destroy() self.flaeche = None self._text_zellen = [] @@ -846,8 +857,6 @@ class RechnungsApp(ThemeMixin, MeldungMixin, KorrekturMixin, EinstellungenMixin, def _fenstersymbol(self): """Das Programmsymbol auf das Fenster legen.""" return wasserzeichen.symbol_setzen(self, dunkel=getattr(self, "_dark_mode", False)) - print("[symbol] assets/programm.ico nicht gefunden") - return False def _hinweis_wenn_sichtbar(self, versuche=0): """Wartet, bis das Fenster gezeichnet ist, und fragt dann. diff --git a/pruef_darstellung.py b/pruef_darstellung.py index 3c076b6..260d444 100644 --- a/pruef_darstellung.py +++ b/pruef_darstellung.py @@ -189,6 +189,66 @@ pruefe(d.winfo_rooty() >= f.winfo_rooty() - 40, d.destroy() pumpen(0.3) +# ---- Der Dialog muss den Klick UEBERLEBEN ------------------------------- +# Gefunden am 07.09.2026 von der Pruefkette: _oberflaeche_neu() loeschte alle +# Kinder des Hauptfensters - und ein Toplevel IST ein Kind. Der offene Dialog +# wurde also beim ersten Klick mitgeloescht, die Zeile danach fasste eine +# Leiche an ("invalid command name .!toplevel"). +# +# Dieser Pruefstand hat es NICHT gefunden, weil er cfg["darstellung"] von Hand +# setzte. Hier wird jetzt der Radioknopf gedrueckt - derselbe Weg, den ein +# Mensch geht. +d = f.dlg_darstellung() +pumpen(1.0) + + +def radioknoepfe(fenster): + """Alle Radiobuttons unterhalb eines Fensters, mit ihrer Beschriftung.""" + treffer = [] + + def durchgehen(w): + for kind in w.winfo_children(): + if kind.winfo_class() == "TRadiobutton": + try: + treffer.append((str(kind.cget("text")), kind)) + except tk.TclError: + pass + durchgehen(kind) + + durchgehen(fenster) + return treffer + + +knoepfe = radioknoepfe(d) +pruefe(len(knoepfe) == len(theme.MODI), + f"der Dialog zeigt alle {len(theme.MODI)} Moeglichkeiten " + f"({[t for t, _ in knoepfe]})") + +ziel = next((w for t, w in knoepfe if t == theme.MODUS_NAMEN["dunkel"]), None) +pruefe(ziel is not None, "der Knopf 'Dunkel' ist da") +if ziel is not None: + ziel.invoke() # der echte Klick + pumpen(1.2) + pruefe(bool(d.winfo_exists()), + "der Dialog lebt nach dem Klick noch") + pruefe(f._dark_mode is True, + f"und die Darstellung ist umgestellt (Hintergrund {f.col_bg})") + # Und noch einmal, denn genau das war vorher unmoeglich: + hell = next((w for t, w in knoepfe if t == theme.MODUS_NAMEN["hell"]), None) + if hell is not None: + try: + hell.invoke() + pumpen(1.0) + pruefe(f._dark_mode is False, + "ein ZWEITER Klick im selben Dialog wirkt auch") + except tk.TclError as e: + pruefe(False, f"der zweite Klick scheiterte ({e})") +try: + d.destroy() +except tk.TclError: + pass +pumpen(0.3) + # ---- Zurueck auf automatisch -------------------------------------------- f.cfg["darstellung"] = "auto" config.save_config(f.cfg)