Knoepfe: "Storno" und "Berichtigen" setzen den Weg schon beim Oeffnen fest, das Popup fragt dann nur noch die Nummer. Vorher entschied ein zweiter Knopf im Popup - der ist raus. app.py war 1802 Zeilen, jetzt 672. Neu daneben: helfer.py Zahlen, Datum, Namen, Pruefungen (87) bestand.py was im Rechnungsordner liegt, nur lesen (171) storno.py den spiegelbildlichen Storno rechnen (57) einzelinstanz.py nur ein Fenster zulassen (90) theme.py Farben nach Windows hell/dunkel (120) korrektur.py Storno- und Berichtigungsweg (306) einstellungen.py die Dialoge aus dem Menue (358) Die Oberflaeche bleibt eine Klasse - die Teile sind Mixins, damit kein einziger self-Zugriff umgeschrieben werden musste. Kein Modul importiert app.py zurueck, keine ungenutzten Importe mehr. Die Pruefstaende holen die Funktionen jetzt aus dem Modul, dem sie gehoeren. pruef_storno.py 100 gruen, pruef_nummern.py 7 gruen, Fenster nach der Aufteilung gestartet und angesehen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EKvGkdNW1vKdnMPAM9Bp3X
120 lines
6 KiB
Python
120 lines
6 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Farben nach dem Windows-Theme (hell/dunkel) - inklusive Titelleiste.
|
|
|
|
Native tk-Menues und -Popups lassen sich unter Windows nicht einfaerben, darum
|
|
baut das Programm Menueleiste und Dropdowns selbst; hier stehen nur die Farben.
|
|
"""
|
|
|
|
import sys
|
|
import tkinter as tk
|
|
from tkinter import ttk
|
|
|
|
|
|
def windows_dark_mode():
|
|
"""True, wenn Windows auf das dunkle App-Theme eingestellt ist."""
|
|
if not sys.platform.startswith("win"):
|
|
return False
|
|
try:
|
|
import winreg
|
|
with winreg.OpenKey(winreg.HKEY_CURRENT_USER,
|
|
r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize") as k:
|
|
return winreg.QueryValueEx(k, "AppsUseLightTheme")[0] == 0
|
|
except OSError:
|
|
return False
|
|
|
|
|
|
class ThemeMixin:
|
|
"""Farben setzen. Erwartet ein tk.Tk als Basis."""
|
|
|
|
# ----------------------------------------------------------------- Theme
|
|
def _apply_theme(self):
|
|
"""Farben passend zum Windows-Theme (hell/dunkel) setzen."""
|
|
dark = windows_dark_mode()
|
|
self._dark_mode = dark
|
|
if dark:
|
|
self.col_bg = "#23272e" # Fenster-Hintergrund
|
|
self.col_fg = "#e6e6e6" # Schrift
|
|
self.col_field = "#2f343c" # Eingabefelder
|
|
self.col_sel = "#3a4a44" # aktiv/hover
|
|
self.col_accent = "#5fb98f" # Akzent (Gruen, hell)
|
|
self.col_line = "#3a3f47"
|
|
self.col_scroll = "#4a505a" # Scroll-Griff
|
|
self.col_muted = "#8a929c" # gedaempfte Schrift (z.B. "powered by")
|
|
else:
|
|
self.col_bg = "#f3f3f3"
|
|
self.col_fg = "#1a1a1a"
|
|
self.col_field = "#ffffff"
|
|
self.col_sel = "#cfe3d8"
|
|
self.col_accent = "#1f5c3d"
|
|
self.col_line = "#cccccc"
|
|
self.col_scroll = "#b8b8b8"
|
|
self.col_muted = "#777777" # gedaempfte Schrift (z.B. "powered by")
|
|
|
|
self.configure(bg=self.col_bg)
|
|
style = ttk.Style(self)
|
|
try:
|
|
style.theme_use("clam")
|
|
except tk.TclError:
|
|
pass
|
|
bg, fg, field, sel, accent = (self.col_bg, self.col_fg, self.col_field,
|
|
self.col_sel, self.col_accent)
|
|
style.configure(".", background=bg, foreground=fg, fieldbackground=field,
|
|
bordercolor=self.col_line, lightcolor=bg, darkcolor=bg)
|
|
style.configure("TFrame", background=bg)
|
|
style.configure("TLabel", background=bg, foreground=fg)
|
|
style.configure("TLabelframe", background=bg, bordercolor=self.col_line)
|
|
style.configure("TLabelframe.Label", background=bg, foreground=accent)
|
|
style.configure("TButton", background=field, foreground=fg, bordercolor=self.col_line)
|
|
style.map("TButton", background=[("active", sel)], foreground=[("active", fg)])
|
|
style.configure("Menu.TMenubutton", background=bg, foreground=fg, relief="flat",
|
|
arrowcolor=bg, padding=(10, 3))
|
|
style.map("Menu.TMenubutton", background=[("active", sel)], foreground=[("active", fg)])
|
|
style.configure("TCheckbutton", background=bg, foreground=fg)
|
|
style.map("TCheckbutton", background=[("active", bg)], foreground=[("active", fg)])
|
|
style.configure("TEntry", fieldbackground=field, foreground=fg, insertcolor=fg)
|
|
# Bei state="readonly" zeichnet die Combobox ihren Text als MARKIERUNG,
|
|
# sobald sie den Fokus hat - ohne diese beiden Farben steht der Wert
|
|
# unsichtbar im Feld.
|
|
style.configure("TCombobox", fieldbackground=field, foreground=fg,
|
|
background=field, arrowcolor=fg,
|
|
selectbackground=field, selectforeground=fg)
|
|
style.map("TCombobox", fieldbackground=[("readonly", field)],
|
|
foreground=[("readonly", fg)], background=[("readonly", field)],
|
|
selectbackground=[("readonly", field)], selectforeground=[("readonly", fg)])
|
|
# Die aufgeklappte Liste ist ein klassisches tk-Listbox-Widget
|
|
self.option_add("*TCombobox*Listbox.background", field)
|
|
self.option_add("*TCombobox*Listbox.foreground", fg)
|
|
self.option_add("*TCombobox*Listbox.selectBackground", sel)
|
|
self.option_add("*TCombobox*Listbox.selectForeground", fg)
|
|
for sbs in ("Vertical.TScrollbar", "Horizontal.TScrollbar", "TScrollbar"):
|
|
style.configure(sbs, troughcolor=bg, background=self.col_scroll,
|
|
bordercolor=bg, lightcolor=bg, darkcolor=bg,
|
|
arrowcolor=fg, relief="flat", gripcount=0)
|
|
style.map(sbs, background=[("active", accent)],
|
|
troughcolor=[("disabled", bg)])
|
|
style.configure("TSeparator", background=self.col_line)
|
|
# Klassische tk-Widgets (Canvas, Toplevel, Text) erben diese Defaults
|
|
self.option_add("*background", bg)
|
|
self.option_add("*foreground", fg)
|
|
def _style_dialog(self, d):
|
|
"""Toplevel-Dialog an das Theme angleichen."""
|
|
d.configure(bg=self.col_bg)
|
|
# erst nach dem Anzeigen setzen, sonst greift DWM noch nicht
|
|
d.after(20, lambda: self._dark_titlebar(d))
|
|
def _dark_titlebar(self, win):
|
|
"""Windows-Titelleiste des Fensters dunkel machen (nur im Dark-Mode)."""
|
|
if not (sys.platform.startswith("win") and getattr(self, "_dark_mode", False)):
|
|
return
|
|
try:
|
|
import ctypes
|
|
win.update_idletasks()
|
|
hwnd = ctypes.windll.user32.GetParent(win.winfo_id())
|
|
val = ctypes.c_int(1)
|
|
for attr in (20, 19): # DWMWA_USE_IMMERSIVE_DARK_MODE (Win11=20, frühes Win10=19)
|
|
ctypes.windll.dwmapi.DwmSetWindowAttribute(
|
|
hwnd, attr, ctypes.byref(val), ctypes.sizeof(val))
|
|
# Frame neu zeichnen lassen (ohne Flackern/Fokusverlust)
|
|
SWP = 0x0001 | 0x0002 | 0x0004 | 0x0020 # NOSIZE|NOMOVE|NOZORDER|FRAMECHANGED
|
|
ctypes.windll.user32.SetWindowPos(hwnd, 0, 0, 0, 0, 0, SWP)
|
|
except Exception as e: # noqa: BLE001
|
|
print("[theme] Titelleiste:", e)
|