Aufraeumen nach der Pruefkette: doppelter Code an eine Stelle
Die Runde vom 07.09.2026 hat neben den zwei echten Fehlern eine Reihe Wiederholungen und stumme Stellen gemeldet. Abgearbeitet in Etappen, jede fuer sich pruefbar - nach jeder liefen beide Pruefketten gruen. * Menueleiste: der Wechsel von einem Titel zum anderen brauchte ZWEI Klicks. Der grab_set der offenen Liste schluckt den ersten, die <Button-1>-Bindung des Titels feuert gar nicht. Das native tk.Menu, das diese Leiste ersetzt hat, wechselte schon beim blossen Ueberfahren - zwei Klicks waren also eine Verschlechterung gegenueber dem, was vorher da war. Die Klappliste wird ausserdem jetzt wie jede andere Position ueber theme.auf_bildschirm() begrenzt; am unteren Fensterrand lief sie vorher hinaus. Neu: pruef_menueleiste.py misst beides ueber echte Klickereignisse. * %LOCALAPPDATA%/ravokk wurde an drei Stellen einzeln ausgerechnet. Jetzt fragen alle gemeinsam.standard_ordner(). Laufen die je auseinander, fuehren die beiden Programme zwei Nummernbuecher - und das faellt erst auf, wenn eine Rechnungsnummer zum zweiten Mal vergeben ist (§ 14 Abs. 4 Nr. 4 UStG). * pruefe() stand 35-mal im Quelltext, in sechs Fassungen -> pruefhelfer.py. Die zwei Ausreisser sind mitgezogen: pruef_bilder.py schrieb ein eigenes Format, pruef_storno_verrechnet.py zaehlte in einer Zahl und konnte am Ende nicht sagen, WAS fehlschlug. * dlg_darstellung() und die drei Handgriffe des Umschaltens standen in beiden app.py fast wortgleich -> ThemeMixin in theme.py. Beide trugen inzwischen denselben langen Kommentar zu demselben Fehler; das war der Beweis, dass es eine Sache ist. Programmspezifisch bleiben der Hinweistext im Dialog (R31) und die Frage, ob die Einstellungen ausdruecklich gespeichert werden muessen. * pruef_gemeinsam_automatisch.py sagte im Text "genau ein Buch" und prueft ">= 1" mit einem any() - eine Pruefung, die nicht rot werden kann. Jetzt == 1, und der Dateiname wird mitgeprueft. Dazu neu: die neun Dateien, die im Kopf zusagen, sie laegen in beiden Programmen gleich, werden byteweise verglichen. Bei hinweis.py stimmte die Zusage seit dem 06.09. nicht mehr - gleicher Inhalt, CRLF gegen LF. * .gitattributes, damit die Zeilenenden nicht von der Maschine abhaengen. Ohne das meldet genau diese Pruefung nach einem frischen Checkout einen Unterschied, den es im Repository gar nicht gibt. * Sechs stumme "except OSError" sagen jetzt, warum sie schweigen duerfen, und einer meldet statt zu schweigen: schlaegt die Uebernahme einer alten config.json fehl, faengt das Programm ohne Firmendaten, Katalog und Zaehler neu an - und der Erfolgsfall schrieb eine Zeile, der Fehlerfall nicht. Die 24 stummen tk.TclError bleiben: dort wird ein Widget angefasst, das gerade zerstoert wurde, und eine Meldung waere Rauschen. * Tote Parameter und Variablen aus dem Umbau des Vortags: symbol_setzen(dunkel=), kachel(grund=, radius=), mit_trennlinie, sechs x/y-Berechnungen, die von theme.mittig() sofort ueberschrieben wurden, vier lokale "import ctypes". 39 Pruefstaende, 0 rot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
This commit is contained in:
parent
5babfc3e2e
commit
2b09d15cd0
36 changed files with 563 additions and 364 deletions
130
theme.py
130
theme.py
|
|
@ -46,7 +46,6 @@ def titelleiste_faerben(fenster, dunkel):
|
|||
if not sys.platform.startswith("win"):
|
||||
return False
|
||||
try:
|
||||
import ctypes
|
||||
fenster.update_idletasks()
|
||||
hwnd = ctypes.windll.user32.GetParent(fenster.winfo_id()) or int(fenster.frame(), 16)
|
||||
wert = ctypes.c_int(1 if dunkel else 0)
|
||||
|
|
@ -84,16 +83,13 @@ def pruefplatz():
|
|||
if not sys.platform.startswith("win"):
|
||||
return ""
|
||||
try:
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
|
||||
class RECT(ctypes.Structure):
|
||||
_fields_ = [("left", ctypes.c_long), ("top", ctypes.c_long),
|
||||
("right", ctypes.c_long), ("bottom", ctypes.c_long)]
|
||||
|
||||
class MONITORINFO(ctypes.Structure):
|
||||
_fields_ = [("cbSize", wintypes.DWORD), ("rcMonitor", RECT),
|
||||
("rcWork", RECT), ("dwFlags", wintypes.DWORD)]
|
||||
_fields_ = [("cbSize", ctypes.wintypes.DWORD), ("rcMonitor", RECT),
|
||||
("rcWork", RECT), ("dwFlags", ctypes.wintypes.DWORD)]
|
||||
|
||||
gefunden = []
|
||||
|
||||
|
|
@ -106,9 +102,9 @@ def pruefplatz():
|
|||
r.left, r.top, r.right - r.left))
|
||||
return 1
|
||||
|
||||
RUECKRUF = ctypes.WINFUNCTYPE(ctypes.c_int, wintypes.HMONITOR,
|
||||
wintypes.HDC, ctypes.POINTER(RECT),
|
||||
wintypes.LPARAM)
|
||||
RUECKRUF = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.wintypes.HMONITOR,
|
||||
ctypes.wintypes.HDC, ctypes.POINTER(RECT),
|
||||
ctypes.wintypes.LPARAM)
|
||||
ctypes.windll.user32.EnumDisplayMonitors(0, 0, RUECKRUF(je_monitor), 0)
|
||||
# Der breiteste Nebenmonitor - bei mehreren der linkeste.
|
||||
neben = sorted((m for m in gefunden if not m[0]),
|
||||
|
|
@ -137,7 +133,6 @@ def auf_bildschirm(x, y, breite=0, hoehe=0):
|
|||
if not sys.platform.startswith("win"):
|
||||
return int(x), int(y)
|
||||
try:
|
||||
import ctypes
|
||||
hole = ctypes.windll.user32.GetSystemMetrics
|
||||
links, oben = hole(76), hole(77)
|
||||
rechts, unten = links + hole(78), oben + hole(79)
|
||||
|
|
@ -207,7 +202,6 @@ def schrift_vorbereiten():
|
|||
_schrift_geladen = True
|
||||
if not sys.platform.startswith("win"):
|
||||
return SCHRIFT_NAME
|
||||
import ctypes
|
||||
FR_PRIVATE = 0x10
|
||||
geladen = 0
|
||||
for basis in _ordner():
|
||||
|
|
@ -615,6 +609,120 @@ class ThemeMixin:
|
|||
"""
|
||||
titelleiste_faerben(win, bool(getattr(self, "_dark_mode", False)))
|
||||
|
||||
# ------------------------------------------------ Hell / dunkel umstellen
|
||||
# Der Hinweistext gehoert dem einzelnen Programm - er sagt etwas ueber
|
||||
# DIESES Programm. Hier steht nur, was dasteht, wenn ihn niemand setzt.
|
||||
DARSTELLUNG_HINWEIS = "Gilt nur für das Programmfenster."
|
||||
|
||||
def _cfg_speichern(self):
|
||||
"""Die Einstellungen wegschreiben - falls das noetig ist.
|
||||
|
||||
Im Rechnungstool ist die Konfiguration ein gewoehnliches Woerterbuch
|
||||
und muss ausdruecklich gespeichert werden. Im Steuerrechnungstool ist
|
||||
sie eine Ablage, die jeden Schreibzugriff sofort in die
|
||||
Journaltabelle durchreicht - dort ist hier nichts zu tun.
|
||||
"""
|
||||
return
|
||||
|
||||
def variablen_sichern(self):
|
||||
"""Alle Tk-Variablen des Fensters als {Name: Wert}.
|
||||
|
||||
WARUM ALLE UND NICHT EINE AUSWAHL
|
||||
---------------------------------
|
||||
Die _build-Methoden legen ihre Variablen selbst an
|
||||
(`self.k_name = tk.StringVar()`). Nach dem Neubau stehen also neue,
|
||||
leere da. Beim ersten Versuch waren Kundenname und Rechnungsnummer
|
||||
weg, waehrend die Mengen blieben - die hatte ich einzeln gesichert.
|
||||
|
||||
Durchgegangen wird deshalb, was das Objekt an Tk-Variablen traegt.
|
||||
Wer spaeter ein Feld hinzufuegt, muss nichts eintragen; eine Liste zum
|
||||
Pflegen waere genau die Sorte, die man beim naechsten Feld vergisst.
|
||||
"""
|
||||
gesichert = {}
|
||||
for name in dir(self):
|
||||
if name.startswith("__"):
|
||||
continue
|
||||
try:
|
||||
wert = getattr(self, name)
|
||||
except Exception: # noqa: BLE001 - Eigenschaften koennen werfen
|
||||
continue
|
||||
if isinstance(wert, (tk.StringVar, tk.IntVar, tk.DoubleVar,
|
||||
tk.BooleanVar)):
|
||||
try:
|
||||
gesichert[name] = wert.get()
|
||||
except tk.TclError:
|
||||
pass
|
||||
return gesichert
|
||||
|
||||
def variablen_zuruecksetzen(self, gesichert):
|
||||
"""Die gesicherten Werte in die neu gebauten Variablen zurueckgeben."""
|
||||
for name, wert in gesichert.items():
|
||||
neu = getattr(self, name, None)
|
||||
if isinstance(neu, (tk.StringVar, tk.IntVar, tk.DoubleVar,
|
||||
tk.BooleanVar)):
|
||||
try:
|
||||
neu.set(wert)
|
||||
except tk.TclError:
|
||||
pass
|
||||
|
||||
def kinder_loeschen(self):
|
||||
"""Die Oberflaeche abraeumen - aber keine fremden Fenster.
|
||||
|
||||
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 hatte es nicht gefunden, weil er cfg["darstellung"] von
|
||||
Hand setzte und _oberflaeche_neu() direkt rief - also alles ausser dem
|
||||
Weg, den ein Mensch geht. Er klickt jetzt.
|
||||
"""
|
||||
for kind in self.winfo_children():
|
||||
if not isinstance(kind, tk.Toplevel):
|
||||
kind.destroy()
|
||||
|
||||
def dlg_darstellung(self):
|
||||
"""Hell, dunkel oder wie Windows - und sofort sichtbar."""
|
||||
d = tk.Toplevel(self)
|
||||
d.title("Darstellung")
|
||||
d.transient(self)
|
||||
d.resizable(False, False)
|
||||
self._style_dialog(d)
|
||||
rahmen = ttk.Frame(d, padding=18)
|
||||
rahmen.pack(fill="both", expand=True)
|
||||
ttk.Label(rahmen, text="Darstellung",
|
||||
font=(SCHRIFT_NAME, 11, "bold"),
|
||||
foreground=self.col_accent).pack(anchor="w")
|
||||
ttk.Label(rahmen, justify="left", wraplength=340,
|
||||
foreground=self.col_muted,
|
||||
text=self.DARSTELLUNG_HINWEIS).pack(anchor="w", pady=(6, 12))
|
||||
|
||||
gewaehlt = tk.StringVar(value=modus(self.cfg))
|
||||
|
||||
def uebernehmen():
|
||||
neu = gewaehlt.get()
|
||||
if neu == modus(self.cfg):
|
||||
return
|
||||
self.cfg["darstellung"] = neu
|
||||
self._cfg_speichern()
|
||||
self._oberflaeche_neu()
|
||||
# Der Dialog gehoert noch zur alten Darstellung - er wird
|
||||
# mitgefaerbt, sonst steht ein helles Fenster vor einem dunklen.
|
||||
self._style_dialog(d)
|
||||
|
||||
for wert in MODI:
|
||||
ttk.Radiobutton(rahmen, text=MODUS_NAMEN[wert], value=wert,
|
||||
variable=gewaehlt, command=uebernehmen).pack(
|
||||
anchor="w", pady=3)
|
||||
|
||||
ttk.Separator(rahmen, orient="horizontal").pack(fill="x", pady=(12, 10))
|
||||
ttk.Button(rahmen, text="Schließen", width=14,
|
||||
command=d.destroy).pack(anchor="e")
|
||||
mittig(d, self)
|
||||
return d
|
||||
|
||||
|
||||
class MeldungMixin:
|
||||
"""Eigene Meldungs- und Ja/Nein-Fenster im Theme des Programms.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue