# -*- coding: utf-8 -*- """Eine Menueleiste, die sich einfaerben laesst. WARUM NICHT DAS MENUE VON TK ---------------------------- `tk.Menu` ist unter Windows kein Tk-Widget, sondern ein Menue des Betriebssystems. Es nimmt Farben zwar entgegen, zeichnet aber trotzdem im Systemgrau - ein weisser Balken ueber einem dunklen Fenster. Sein Befund am 07.09.2026 zum Steuerrechnungstool: *"nur die einstellunden hilfe leiste die ist noch hell"*. Diese Leiste ist deshalb aus gewoehnlichen Labels gebaut: eine Zeile mit den Titeln, und beim Klick geht ein randloses Toplevel als Klappliste auf. Beides traegt die Farben des Programms. WAS SIE KANN UND WAS NICHT -------------------------- Sie kann, was diese beiden Programme brauchen: Titel, Eintraege, Trennlinien. Sie kann bewusst NICHT: Untermenues, Tastenkuerzel neben dem Eintrag, Ankreuzfelder. Wer das braucht, baut es hier ein - nicht daneben. BENUTZUNG self.menue = menueleiste.Menueleiste(self, farben=self, menues={ "Datei": [("Beenden", self._beenden)], "Hilfe": [("Info", self._info)], }) `farben` ist das Fenster mit den col_*-Feldern (das ThemeMixin). Ein "-" als Beschriftung ergibt eine Trennlinie. Diese Datei liegt in BEIDEN Programmen gleich. Wer sie aendert, kopiert sie hinueber. """ import theme import tkinter as tk from tkinter import ttk class Menueleiste(tk.Frame): def __init__(self, master, menues, farben=None): self.f = farben if farben is not None else master super().__init__(master, bg=self.f.col_bg) self.menues = menues self._offen = None self._anker = None # welcher Titel gerade offen ist self._titel = {} # Label -> Eintraege, fuer den Wechsel im Klick self.pack(side="top", fill="x") for titel, eintraege in menues.items(): lbl = tk.Label(self, text=titel, bg=self.f.col_bg, fg=self.f.col_fg, padx=8, pady=3) lbl.pack(side="left") self._titel[lbl] = eintraege lbl.bind("", lambda e, w=lbl: w.configure(bg=self.f.col_sel)) lbl.bind("", lambda e, w=lbl: w.configure(bg=self.f.col_bg)) lbl.bind("", lambda e, b=lbl, it=eintraege: self.aufklappen(b, it)) ttk.Separator(master, orient="horizontal").pack(side="top", fill="x") # ---------------------------------------------------------------- Klappen def aufklappen(self, anker, eintraege): if self._offen: # zweiter Klick schliesst wieder self.zuklappen() return pop = tk.Toplevel(self) pop.overrideredirect(True) # Der Rahmen ist der Hintergrund des Toplevels, einen Pixel breit - # sonst zeichnet Windows einen weissen Rand um die Liste. pop.configure(bg=self.f.col_line) innen = tk.Frame(pop, bg=self.f.col_field) innen.pack(padx=1, pady=1) for beschriftung, befehl in eintraege: if beschriftung == "-": tk.Frame(innen, bg=self.f.col_line, height=1).pack(fill="x", pady=3) continue eintrag = tk.Label(innen, text=beschriftung, bg=self.f.col_field, fg=self.f.col_fg, anchor="w", padx=16, pady=5) eintrag.pack(fill="x") eintrag.bind("", lambda e, w=eintrag: w.configure(bg=self.f.col_sel)) eintrag.bind("", lambda e, w=eintrag: w.configure(bg=self.f.col_field)) eintrag.bind("", lambda e, c=befehl: self._ausfuehren(c)) pop.update_idletasks() # Auf den sichtbaren Bereich begrenzen, wie jede andere Position im # Programm auch. Steht das Fenster am unteren oder rechten Rand, lief # die Liste sonst hinaus und war nicht mehr zu treffen. x, y = theme.auf_bildschirm( anker.winfo_rootx(), anker.winfo_rooty() + anker.winfo_height(), pop.winfo_reqwidth(), pop.winfo_reqheight()) pop.geometry(f"+{x}+{y}") self._offen = pop self._anker = anker pop.grab_set() pop.bind("", self._klick_ausserhalb, add="+") pop.bind("", lambda e: self.zuklappen()) faerben = getattr(self.f, "_dark_titlebar", None) if faerben: faerben(pop) def _klick_ausserhalb(self, ereignis): """Klick neben die Liste: schliessen - und auf einem anderen Titel gleich dessen Liste aufklappen. WARUM DAS HIER STEHT UND NICHT IM LABEL (07.09.2026) ---------------------------------------------------- Solange eine Liste offen ist, haelt ihr `grab_set` alle Mausklicks der Anwendung fest. Die ``-Bindung des Menuetitels feuert dann gar nicht - der Klick landet hier. Vorher endete er damit beim blossen Zuklappen, und man musste ein zweites Mal klicken, um von "Datei" zu "Hilfe" zu kommen. 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. Ein Klick auf DENSELBEN Titel schliesst weiterhin nur - sonst koennte man eine Liste nie ueber ihren Titel wieder loswerden. """ pop = self._offen if not pop: return if (pop.winfo_rootx() <= ereignis.x_root <= pop.winfo_rootx() + pop.winfo_width() and pop.winfo_rooty() <= ereignis.y_root <= pop.winfo_rooty() + pop.winfo_height()): return # der Klick lag in der Liste vorher = self._anker self.zuklappen() getroffen = self._titel_bei(ereignis.x_root, ereignis.y_root) if getroffen is not None and getroffen is not vorher: self.aufklappen(getroffen, self._titel[getroffen]) def _titel_bei(self, x_root, y_root): """Welcher Menuetitel an dieser Bildschirmstelle liegt - oder None. Nachgerechnet statt gefragt: `winfo_containing()` waere kuerzer, gibt aber nichts zurueck, sobald das Fenster einer ANDEREN Anwendung ueber dem Punkt liegt. Beim Bedienen ist das nie der Fall - beim Pruefen schon, und dort meldete es dann einen Fehler, den es nicht gibt (07.09.2026, zwei Pruefstaende liefen gleichzeitig auf demselben Bildschirmplatz). Wo die eigenen Titel liegen, weiss diese Leiste selbst; dafuer muss sie das Fenstersystem nicht fragen. """ for lbl in self._titel: x, y = lbl.winfo_rootx(), lbl.winfo_rooty() if (x <= x_root <= x + lbl.winfo_width() and y <= y_root <= y + lbl.winfo_height()): return lbl return None def _ausfuehren(self, befehl): # Erst zuklappen, dann ausfuehren: sonst haelt der grab_set der Liste # einen Dialog fest, der aus dem Eintrag heraus aufgeht. self.zuklappen() if befehl: befehl() def zuklappen(self): if self._offen: try: self._offen.grab_release() self._offen.destroy() except tk.TclError: pass self._offen = None self._anker = None