# -*- 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)