Absturz beim Ziehen des Fensters behoben, Zeiger am Rand wieder über Tk, Vodafone-Datum in Spalte A
Windows-Build / windows-exe (push) Successful in 5m30s
Windows-Build / windows-exe (push) Successful in 5m30s
- Verschieben/Größe ziehen per PostMessage statt SendMessage: Die Windows-Verschiebeschleife lief innerhalb eines Tk-Ereignisses; Tk führte darin Python-Ereignisse (<Configure>) aus -> harter Absturz „PyEval_RestoreThread … thread state is NULL“ (normal und aus maximiert). - Größen-Mauszeiger am Rand wieder von Tk (blieb sonst im Fenster stehen); Windows zieht beim Drücken. - Neuer Maustest im Build (xdotool, echte Maus): Titelleiste/Rand ziehen, maximieren, herausziehen. - Export: Bei Vodafone steht in Spalte A das Aktivierungsdatum (statt Abrechnungslauf). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
966379293a
commit
5a9d6d19a2
11 files changed
+166
-71
No files matched your search
+22
-48
@@ -11,11 +11,17 @@ Größenänderungen also weiter ohne Rahmenaufschlag (sonst würde das Fenster b
|
||||
Größenanpassung um die Rahmenbreite wachsen). Windows erledigt alles Weitere selbst:
|
||||
- Ziehen an der Titelleiste (WM_NCLBUTTONDOWN/HTCAPTION) mit Andocken links/rechts/oben (Maximieren)
|
||||
- Win+Pfeiltasten, Doppelklick, Systemmenü (Alt+Leertaste), Taskleiste, Alt+Tab
|
||||
- Größe ändern: Am Rand meldet das Fenster per WM_NCHITTEST „linker Rand“ usw. -> Windows zeigt den
|
||||
passenden Mauszeiger und zieht die Größe selbst (flüssig, kein hängender Zeiger).
|
||||
- Größe ändern: Den Größen-Mauszeiger am Rand zeigt Tk (rahmen.py) – so weiß Tk immer, welcher Zeiger
|
||||
gilt, und setzt ihn beim Zurückfahren in das Fenster wieder richtig. Beim Drücken auf den Rand zieht
|
||||
Windows die Größe (flüssig).
|
||||
|
||||
WICHTIG: Die Fensterprozeduren hier laufen mitten in Tk. Sie dürfen Tk NICHT aufrufen (Tk-Sperre ->
|
||||
Verklemmung), nur Win32-Funktionen.
|
||||
WICHTIG:
|
||||
- Die Fensterprozeduren hier laufen mitten in Tk. Sie dürfen Tk NICHT aufrufen (Tk-Sperre ->
|
||||
Verklemmung), nur Win32-Funktionen.
|
||||
- Verschieben/Größe ziehen NIE synchron (SendMessage) aus einem Tk-Ereignis heraus starten: Windows
|
||||
läuft dann in einer eigenen Schleife, in der Tk Python-Ereignisse (z. B. <Configure>) ausführt,
|
||||
während Python das nicht erwartet -> harter Absturz („PyEval_RestoreThread … thread state is
|
||||
NULL“). Deshalb PostMessage: Die Schleife startet, sobald Tk wieder in seiner Ereignisschleife ist.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -27,15 +33,13 @@ from ctypes import wintypes
|
||||
IS_WIN = sys.platform.startswith("win")
|
||||
|
||||
WM_NCDESTROY, WM_NCCALCSIZE, WM_NCHITTEST, WM_NCACTIVATE = 0x0082, 0x0083, 0x0084, 0x0086
|
||||
WM_NCLBUTTONDOWN, WM_SYSCOMMAND, WM_SETCURSOR = 0x00A1, 0x0112, 0x0020
|
||||
HTCLIENT, HTCAPTION, HTTRANSPARENT = 1, 2, -1
|
||||
WM_NCLBUTTONDOWN, WM_SYSCOMMAND = 0x00A1, 0x0112
|
||||
HTCLIENT, HTCAPTION = 1, 2
|
||||
HT = {"w": 10, "e": 11, "n": 12, "nw": 13, "ne": 14, "s": 15, "sw": 16, "se": 17}
|
||||
GWL_STYLE, GWL_EXSTYLE = -16, -20
|
||||
WS_CAPTION, WS_THICKFRAME, WS_SYSMENU = 0x00C00000, 0x00040000, 0x00080000
|
||||
WS_MINIMIZEBOX, WS_MAXIMIZEBOX = 0x00020000, 0x00010000
|
||||
WS_EX_TOOLWINDOW, WS_EX_APPWINDOW = 0x00000080, 0x00040000
|
||||
ZEIGER = {"n": 32645, "s": 32645, "w": 32644, "e": 32644, "nw": 32642, "se": 32642, "ne": 32643, "sw": 32643}
|
||||
HT_RAND = {v: k for k, v in HT.items()}
|
||||
SWP_NOSIZE, SWP_NOMOVE, SWP_NOZORDER, SWP_NOACTIVATE, SWP_FRAMECHANGED = 0x1, 0x2, 0x4, 0x10, 0x20
|
||||
SC_MAXIMIZE, SC_RESTORE, SC_MINIMIZE = 0xF030, 0xF120, 0xF020
|
||||
|
||||
@@ -73,9 +77,7 @@ def _api():
|
||||
("GetMonitorInfoW", [wintypes.HMONITOR, ctypes.POINTER(MONITORINFO)], wintypes.BOOL),
|
||||
("ReleaseCapture", [], wintypes.BOOL), ("SendMessageW", [H, U, W, L], LRESULT),
|
||||
("PostMessageW", [H, U, W, L], wintypes.BOOL), ("ShowWindow", [H, ctypes.c_int], wintypes.BOOL),
|
||||
("WindowFromPoint", [wintypes.POINT], H),
|
||||
("LoadCursorW", [wintypes.HINSTANCE, ctypes.c_void_p], wintypes.HANDLE),
|
||||
("SetCursor", [wintypes.HANDLE], wintypes.HANDLE)):
|
||||
("WindowFromPoint", [wintypes.POINT], H)):
|
||||
f = getattr(u, name)
|
||||
f.argtypes, f.restype = args, res
|
||||
c.SetWindowSubclass.argtypes = [H, SUBCLASSPROC, ctypes.c_size_t, ctypes.c_size_t]
|
||||
@@ -109,11 +111,7 @@ def arbeitsbereich_von(rect: wintypes.RECT) -> wintypes.RECT | None:
|
||||
|
||||
|
||||
class NativerRahmen:
|
||||
"""Macht das Tk-Hauptfenster zu einem echten Windows-Fenster ohne sichtbaren Windows-Rahmen.
|
||||
|
||||
rand_widgets: Tk-Widgets, die am Fensterrand liegen (deren Randbereich wird für die Maus
|
||||
„durchsichtig“, damit Windows dort den Größenrahmen erkennt).
|
||||
"""
|
||||
"""Macht das Tk-Hauptfenster zu einem echten Windows-Fenster ohne sichtbaren Windows-Rahmen."""
|
||||
|
||||
def __init__(self, root: tk.Tk, rand: int, ecke: int):
|
||||
self.root = root
|
||||
@@ -121,11 +119,10 @@ class NativerRahmen:
|
||||
self.hwnd = None
|
||||
self.aktiv = False
|
||||
self._procs: dict[int, object] = {} # Referenzen halten, sonst räumt Python die Callbacks weg
|
||||
self._rand_hwnds: set[int] = set()
|
||||
|
||||
# ------------------------------------------------------------------ Einrichten
|
||||
def einrichten(self, rand_widgets: list[tk.Misc]) -> bool:
|
||||
"""Nach dem ersten Anzeigen aufrufen (vorher gibt es das äußere Windows-Fenster noch nicht)."""
|
||||
def einrichten(self) -> bool:
|
||||
"""Sobald Tk das äußere Windows-Fenster angelegt hat (möglichst vor dem ersten Anzeigen)."""
|
||||
if self.aktiv:
|
||||
return True
|
||||
try:
|
||||
@@ -142,10 +139,6 @@ class NativerRahmen:
|
||||
u.SetWindowLongW(h, GWL_EXSTYLE, (ex & ~WS_EX_TOOLWINDOW) | WS_EX_APPWINDOW)
|
||||
if not self._unterklasse(h, self._aussen_proc):
|
||||
return False
|
||||
for wdg in rand_widgets:
|
||||
hw = wdg.winfo_id()
|
||||
if self._unterklasse(hw, self._rand_proc):
|
||||
self._rand_hwnds.add(hw)
|
||||
self.aktiv = True
|
||||
# Rahmen neu berechnen lassen (jetzt: unsichtbar)
|
||||
u.SetWindowPos(h, None, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE
|
||||
@@ -167,10 +160,9 @@ class NativerRahmen:
|
||||
"""Hat Tk das äußere Fenster neu angelegt (z. B. nach „wm resizable“)? Dann neu einrichten."""
|
||||
return self.aktiv and self.aeusseres_fenster() not in (None, self.hwnd)
|
||||
|
||||
def neu_einrichten(self, rand_widgets: list[tk.Misc]) -> bool:
|
||||
def neu_einrichten(self) -> bool:
|
||||
self.aktiv = False
|
||||
self._rand_hwnds.clear()
|
||||
return self.einrichten(rand_widgets)
|
||||
return self.einrichten()
|
||||
|
||||
def _unterklasse(self, h: int, fn) -> bool:
|
||||
_, c = _api()
|
||||
@@ -229,27 +221,12 @@ class NativerRahmen:
|
||||
if rand:
|
||||
return HT[rand]
|
||||
return HTCLIENT
|
||||
if msg == WM_SETCURSOR and (lparam & 0xFFFF) in HT_RAND:
|
||||
u = _api()[0]
|
||||
u.SetCursor(u.LoadCursorW(None, ZEIGER[HT_RAND[lparam & 0xFFFF]]))
|
||||
return 1
|
||||
if msg == WM_NCDESTROY:
|
||||
self._entfernen(hwnd)
|
||||
except Exception: # niemals eine Ausnahme in die Fensterprozedur durchlassen
|
||||
pass
|
||||
return c.DefSubclassProc(hwnd, msg, wparam, lparam)
|
||||
|
||||
def _rand_proc(self, hwnd, msg, wparam, lparam, _id, _ref):
|
||||
_, c = _api()
|
||||
try:
|
||||
if msg == WM_NCHITTEST and self.randbereich(*_xy(lparam)):
|
||||
return HTTRANSPARENT # Maus „fällt durch“ zum äußeren Fenster -> Größenrahmen
|
||||
if msg == WM_NCDESTROY:
|
||||
self._entfernen(hwnd)
|
||||
except Exception:
|
||||
pass
|
||||
return c.DefSubclassProc(hwnd, msg, wparam, lparam)
|
||||
|
||||
def _entfernen(self, hwnd):
|
||||
proc = self._procs.pop(hwnd, None)
|
||||
if proc is not None:
|
||||
@@ -262,15 +239,16 @@ class NativerRahmen:
|
||||
|
||||
# ------------------------------------------------------------------ Bedienung
|
||||
def verschieben(self) -> None:
|
||||
"""Titelleiste gedrückt: Windows zieht das Fenster (mit Andocken an den Bildschirmrand)."""
|
||||
"""Titelleiste gedrückt: Windows zieht das Fenster (mit Andocken an den Bildschirmrand).
|
||||
PostMessage, nicht SendMessage – siehe Modulbeschreibung."""
|
||||
u, _ = _api()
|
||||
u.ReleaseCapture()
|
||||
u.SendMessageW(self.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0)
|
||||
u.PostMessageW(self.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0)
|
||||
|
||||
def groesse_ziehen(self, rand: str) -> None:
|
||||
u, _ = _api()
|
||||
u.ReleaseCapture()
|
||||
u.SendMessageW(self.hwnd, WM_NCLBUTTONDOWN, HT[rand], 0)
|
||||
u.PostMessageW(self.hwnd, WM_NCLBUTTONDOWN, HT[rand], 0)
|
||||
|
||||
def maximiert(self) -> bool:
|
||||
return bool(self.hwnd and _api()[0].IsZoomed(self.hwnd))
|
||||
@@ -298,10 +276,6 @@ class NativerRahmen:
|
||||
u, _ = _api()
|
||||
return u.GetWindowLongW(self.hwnd, GWL_STYLE), u.GetWindowLongW(self.hwnd, GWL_EXSTYLE)
|
||||
|
||||
def fenster_bei(self, x: int, y: int) -> int | None:
|
||||
"""Für Tests: Welches Fenster bekommt die Maus am Bildschirmpunkt (berücksichtigt HTTRANSPARENT)?"""
|
||||
return _api()[0].WindowFromPoint(wintypes.POINT(x, y))
|
||||
|
||||
def trefferpruefung(self, hwnd: int, x: int, y: int) -> int:
|
||||
"""Für Tests: Was meldet das Fenster `hwnd` für den Bildschirmpunkt (WM_NCHITTEST)?"""
|
||||
lp = ((y & 0xFFFF) << 16) | (x & 0xFFFF)
|
||||
|
||||
Reference in new issue
Block a user