Popups kommen nach Klick woanders / Alt+Tab wieder nach vorn; Büroklammer im Tipp des Tages

Fehler: War ein Popup offen und man klickte in ein anderes Programm, ließ sich das Popup über
Hauptfenster, Taskleiste oder Alt+Tab nicht mehr nach vorn holen.
Ursache: Rahmenlose Tk-Fenster mit transient() legt Tk unter Windows als Kindfenster des Desktops
(WS_CHILD) an – ohne Besitzer, daher kam beim Zurückholen nur das (gesperrte) Hauptfenster nach vorn.

- Popups (Dialoge, Rückfragen, Rundgang) ohne transient(), stattdessen Windows-Besitzer +
  Sperre des aufrufenden Fensters wie bei echten modalen Windows-Dialogen (gui/win32.py)
- Rundgang: neue Sprechblase erscheint vor dem Entfernen der alten (sonst springt Windows weg)
- Alt+F4 und Modalsperre robust gegen Timing (Sperre wird bei Bedarf nachgeholt)
- Tests (nur unter Windows/Wine): Besitzer, Sperre, Stapelreihenfolge nach „woanders klicken“
  und Zurückholen, verschachtelte Rückfragen, Rundgang
- Tipp des Tages mit freigestelltem Büroklammer-Bild (build/make_tipp_bild.py), in der .exe
  enthalten und vom Selbsttest geprüft

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-11-02 11:26:17 +01:00
1 parent aae28fa7f2
commit 26ac90bb0f
10 files changed
+417 -29

No files matched your search

+31 -5
View File
@@ -508,9 +508,13 @@ class Dialog(tk.Toplevel):
super().__init__(master, bg=FACE)
self.withdraw()
self.title(titel)
self.transient(master.winfo_toplevel())
if not KLASSISCH:
# Rahmenlose Dialoge bekommen ihren Besitzer über win32.Modal – mit transient() würde Tk
# sie unter Windows als Kindfenster anlegen, die dann hinter anderen Programmen hängen.
self.transient(master.winfo_toplevel())
self.resizable(resizable, resizable)
self.result = None
self._modal = None # Windows: Besitzer + Sperre des aufrufenden Fensters (siehe win32.Modal)
self.protocol("WM_DELETE_WINDOW", self.abbrechen)
self.bind("<Escape>", lambda e: self.abbrechen())
try:
@@ -537,11 +541,14 @@ class Dialog(tk.Toplevel):
self.geometry(f"+{max(0, x)}+{max(0, y)}")
self.deiconify()
self.lift()
# Windows: Dialog gehört dem aufrufenden Fenster, das solange gesperrt ist → Klick aufs
# Hauptfenster, Taskleiste und Alt+Tab holen automatisch den Dialog nach vorn.
from provtool.gui.win32 import Modal
self.update_idletasks()
self.focus_force()
self._modal = Modal(self, m)
vorher = self.grab_current() # z. B. der Assistent, wenn hier eine Rückfrage aufgeht
try:
self.grab_set()
except tk.TclError:
pass
self._sperre_setzen()
bind_id = None
if KLASSISCH:
# Ein rahmenloser Dialog darf nicht hinter dem Hauptfenster verschwinden
@@ -563,6 +570,25 @@ class Dialog(tk.Toplevel):
pass
return self.result
def _sperre_setzen(self, versuch: int = 0):
"""Tk-Modalsperre setzen. Ist das Fenster noch nicht ganz sichtbar (langsamer PC, Wine),
schlägt grab_set fehl – dann kurz danach erneut versuchen statt ohne Sperre weiterzumachen."""
try:
if self.winfo_exists():
self.grab_set()
except tk.TclError:
if versuch < 50:
self.after(20, lambda: self._sperre_setzen(versuch + 1))
def destroy(self):
# Erst das aufrufende Fenster wieder freigeben, dann schließen – sonst aktiviert Windows
# beim Schließen ein fremdes Programm statt unseres Fensters.
modal = getattr(self, "_modal", None)
if modal is not None:
modal.beenden()
self._modal = None
super().destroy()
def abbrechen(self):
self.result = None
self.destroy()