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
90 lines
3.2 KiB
Python
90 lines
3.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Nur ein Fenster zulassen und das offene nach vorn holen.
|
|
|
|
Zwei Fenster lesen beim Start denselben Zähler und schlagen dieselbe
|
|
Rechnungsnummer vor - die Nummer wird dann doppelt vergeben.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
import config
|
|
|
|
|
|
def einzelinstanz_pruefen():
|
|
"""Sperrt das Programm gegen ein zweites Fenster.
|
|
|
|
Beide Fenster lesen beim Start denselben Zaehler aus der config und
|
|
schlagen darum dieselbe Rechnungsnummer vor -> die Nummer wird doppelt
|
|
vergeben. Darum: nur eine Instanz je Windows-Sitzung.
|
|
|
|
Rueckgabe: Handle (offen halten, solange das Programm laeuft) oder
|
|
False, wenn schon eine Instanz laeuft.
|
|
"""
|
|
if not sys.platform.startswith("win"):
|
|
return None
|
|
import ctypes
|
|
from ctypes import wintypes
|
|
ERROR_ALREADY_EXISTS = 183
|
|
k32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
|
k32.CreateMutexW.argtypes = [wintypes.LPVOID, wintypes.BOOL, wintypes.LPCWSTR]
|
|
k32.CreateMutexW.restype = wintypes.HANDLE
|
|
handle = k32.CreateMutexW(None, False, "RechnungstoolCampinghofBartl")
|
|
if ctypes.get_last_error() == ERROR_ALREADY_EXISTS:
|
|
return False
|
|
return handle
|
|
# Merkzettel mit der PID der laufenden Instanz - liegt neben der config.json.
|
|
PID_DATEI = os.path.join(config.BASE_DIR, "rechnungstool.pid")
|
|
def eigene_pid_merken():
|
|
"""Schreibt die eigene PID, damit eine zweite Instanz das Fenster findet."""
|
|
try:
|
|
with open(PID_DATEI, "w", encoding="ascii") as f:
|
|
f.write(str(os.getpid()))
|
|
except OSError:
|
|
pass
|
|
def laufende_pid_lesen():
|
|
"""PID der laufenden Instanz aus dem Merkzettel; None wenn unlesbar."""
|
|
try:
|
|
with open(PID_DATEI, "r", encoding="ascii") as f:
|
|
return int(f.read().strip())
|
|
except (OSError, ValueError):
|
|
return None
|
|
def fenster_nach_vorn(pid):
|
|
"""Holt das Fenster des Prozesses <pid> nach vorn. True bei Erfolg.
|
|
|
|
Gesucht wird ueber die PID, nicht ueber den Fenstertitel - der Titel
|
|
haengt am Firmennamen aus der config und aendert sich mit ihm.
|
|
"""
|
|
if not pid or not sys.platform.startswith("win"):
|
|
return False
|
|
import ctypes
|
|
from ctypes import wintypes
|
|
SW_RESTORE = 9
|
|
u32 = ctypes.WinDLL("user32", use_last_error=True)
|
|
u32.IsWindowVisible.argtypes = [wintypes.HWND]
|
|
u32.IsIconic.argtypes = [wintypes.HWND]
|
|
u32.ShowWindow.argtypes = [wintypes.HWND, ctypes.c_int]
|
|
u32.SetForegroundWindow.argtypes = [wintypes.HWND]
|
|
u32.GetWindowTextLengthW.argtypes = [wintypes.HWND]
|
|
u32.GetWindowThreadProcessId.argtypes = [wintypes.HWND, ctypes.POINTER(wintypes.DWORD)]
|
|
|
|
treffer = []
|
|
|
|
def besuch(hwnd, _lparam):
|
|
fremd = wintypes.DWORD()
|
|
u32.GetWindowThreadProcessId(hwnd, ctypes.byref(fremd))
|
|
# nur das echte Hauptfenster: sichtbar und mit Titel
|
|
if fremd.value == pid and u32.IsWindowVisible(hwnd) and u32.GetWindowTextLengthW(hwnd):
|
|
treffer.append(hwnd)
|
|
return False
|
|
return True
|
|
|
|
CB = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
|
|
u32.EnumWindows(CB(besuch), 0)
|
|
if not treffer:
|
|
return False
|
|
hwnd = treffer[0]
|
|
if u32.IsIconic(hwnd):
|
|
u32.ShowWindow(hwnd, SW_RESTORE)
|
|
u32.SetForegroundWindow(hwnd)
|
|
return True
|