Phase 2: Launcher-UI + eigenstaendige Modul-Fenster - Form1 -> LauncherForm (Dateien via git mv, Designer/resx angepasst) - Modul-Tab: Karten aus ModuleRegistry mit "Fenster oeffnen"-Button je Modul - UI/WindowManager: Fenster-Tracking (Key->Form), Re-Open fokussiert, CloseAll - UI/ModuleFormBase: Basisklasse fuer eigenstaendige Modul-Fenster - CongressTradingForm: DB-Kennzahlen + manueller Scrape-Trigger (Phase-4-Ausbau folgt) - WindowManager in DI; Launcher schliesst Modul-Fenster beim Beenden - Tests: WindowManager (6) -> 21/21 gruen; Launcher-Start verifiziert Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> @
83 lines
2.7 KiB
C#
83 lines
2.7 KiB
C#
namespace IBKRTrader.UI;
|
|
|
|
/// <summary>
|
|
/// Verwaltet die eigenständigen Modul-Fenster des Launchers.
|
|
/// Pro Modul-Key wird höchstens ein Fenster gehalten: erneutes Öffnen
|
|
/// fokussiert das bestehende Fenster statt ein zweites zu erzeugen.
|
|
///
|
|
/// Die tatsächlichen UI-Aktionen (Show/Focus) sind in überschreibbare
|
|
/// Methoden gekapselt, damit die Tracking-Logik ohne Message-Loop testbar ist.
|
|
/// </summary>
|
|
public class WindowManager
|
|
{
|
|
private readonly Dictionary<string, Form> _open = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
/// <summary>Öffnet das Fenster zum Key oder fokussiert ein bereits offenes.</summary>
|
|
public Form OpenOrFocus(string key, Func<Form> factory)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(key);
|
|
ArgumentNullException.ThrowIfNull(factory);
|
|
|
|
if (_open.TryGetValue(key, out var existing))
|
|
{
|
|
if (!existing.IsDisposed)
|
|
{
|
|
Focus(existing);
|
|
return existing;
|
|
}
|
|
_open.Remove(key);
|
|
}
|
|
|
|
var form = factory();
|
|
_open[key] = form;
|
|
form.FormClosed += OnFormClosed;
|
|
Present(form);
|
|
return form;
|
|
}
|
|
|
|
/// <summary>True, wenn zum Key aktuell ein nicht-disposetes Fenster offen ist.</summary>
|
|
public bool IsOpen(string key)
|
|
=> _open.TryGetValue(key, out var f) && !f.IsDisposed;
|
|
|
|
/// <summary>Anzahl aktuell offener Fenster.</summary>
|
|
public int OpenCount => _open.Values.Count(f => !f.IsDisposed);
|
|
|
|
/// <summary>Schließt alle offenen Fenster (z. B. beim Beenden des Launchers).</summary>
|
|
public void CloseAll()
|
|
{
|
|
foreach (var f in _open.Values.ToList())
|
|
{
|
|
if (!f.IsDisposed)
|
|
{
|
|
f.FormClosed -= OnFormClosed;
|
|
f.Close();
|
|
}
|
|
}
|
|
_open.Clear();
|
|
}
|
|
|
|
// ─── Überschreibbare UI-Aktionen (für Tests) ──────────────────────────────
|
|
|
|
/// <summary>Zeigt ein neu erzeugtes Fenster an.</summary>
|
|
protected virtual void Present(Form form) => form.Show();
|
|
|
|
/// <summary>Bringt ein bestehendes Fenster in den Vordergrund.</summary>
|
|
protected virtual void Focus(Form form)
|
|
{
|
|
if (form.WindowState == FormWindowState.Minimized)
|
|
form.WindowState = FormWindowState.Normal;
|
|
form.Activate();
|
|
form.BringToFront();
|
|
}
|
|
|
|
private void OnFormClosed(object? sender, FormClosedEventArgs e)
|
|
{
|
|
if (sender is not Form f) return;
|
|
f.FormClosed -= OnFormClosed;
|
|
|
|
var entry = _open.FirstOrDefault(p => ReferenceEquals(p.Value, f));
|
|
if (entry.Key is not null)
|
|
_open.Remove(entry.Key);
|
|
}
|
|
}
|