R1: Umbau auf Multi-Projekt-Struktur (Vorbild PolytraderSharp) Kurskorrektur: Vorbild ist PolytraderSharp (C#), nicht die veraltete Python-Version. Reiner Strukturumbau, Verhalten unveraendert. - src/IBKRTrader.Core (classlib): Core-Code + UI-Contract (ModuleFormBase, WindowManager) - src/IBKRTrader.Modules.CongressTrading (classlib, referenziert nur Core) - Root: IBKRTrader.App (WinExe) referenziert Core + Modul - tests/IBKRTrader.Tests: Referenzen auf Core + Modul - Neue .slnx; ungenutztes HtmlAgilityPack entfernt - docs/ARCHITECTURE.md: korrigierter Ziel-Stand + R-Phasenplan - Build + 38/38 Tests gruen; App startet 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);
|
|
}
|
|
}
|