Zwei getrennte Knoepfe und app.py in Module aufgeteilt
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
This commit is contained in:
parent
d3962ec8d1
commit
2277a12e0f
10 changed files with 1225 additions and 1147 deletions
120
theme.py
Normal file
120
theme.py
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
# -*- 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue