Files
IBKRTrader/UI/ShellUiHost.cs
Richard 8f44c0fd82 @
R2: Generic Host + Modul-Vertrag + Shell-UI (PolytraderSharp-Konzept)

- Core/Modularity: neuer IModule (Name, DbPrefix, RegisterServices(services,config),
  RegisterUi(host,sp), StartAsync/StopAsync, GetActivationBlocker) + ModuleView
  + IModuleUiHost + WindowMenu. Alte IModule/ModuleRegistry/WindowManager/ModuleFormBase entfernt.
- Program.cs: Host.CreateDefaultBuilder + IConfiguration (appsettings.json/.Local.json);
  Core-Services registriert, Module via RegisterServices, Views via RegisterUi.
- UI: ShellUiHost (Einzelinstanz-Fenster + Fenster-Menue), LauncherForm als Shell
  (Buttons je View), Core-Views Logs/Settings/Workers als eigene Fenster.
- CongressTrading auf neuen Vertrag; Worker als IWorker registriert.
- --smoke-ui Headless-Test (konstruiert jede View + Launcher).
- appsettings.Local.json gitignored.
- Tests angepasst -> 30/30 gruen; Build + Smoke-UI + App-Start verifiziert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-28 00:24:00 +02:00

108 lines
3.7 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using IBKRTrader.Core.Modularity;
namespace IBKRTrader.UI;
/// <summary>
/// Verwaltet die registrierten Fenster-Views: öffnet sie als eigenständige Forms, hält je View
/// höchstens eine Instanz offen und holt ein bereits offenes Fenster wieder in den Vordergrund.
/// Meldet Änderungen am Offen-Status (für die Button-Markierung im Launcher).
/// Vorbild: PolytraderSharp <c>ShellUiHost</c>.
/// </summary>
public sealed class ShellUiHost : IModuleUiHost
{
private readonly List<ModuleView> _views = new();
private readonly Dictionary<string, Form> _open = new(StringComparer.OrdinalIgnoreCase);
private Form? _mainWindow;
/// <summary>True, sobald das Beenden bestätigt wurde (der Launcher wertet das in FormClosing aus).</summary>
public bool ShutdownConfirmed { get; private set; }
public event Action? OpenStateChanged;
public IReadOnlyList<ModuleView> Views => _views;
public void RegisterView(ModuleView view) => _views.Add(view);
/// <summary>Setzt das Hauptfenster (Launcher) Ziel für <see cref="ActivateMain"/>.</summary>
public void SetMainWindow(Form main) => _mainWindow = main;
public bool IsOpen(string viewId) =>
_open.TryGetValue(viewId, out var form) && !form.IsDisposed;
public void ActivateMain()
{
if (_mainWindow is null || _mainWindow.IsDisposed) return;
if (_mainWindow.WindowState == FormWindowState.Minimized)
_mainWindow.WindowState = FormWindowState.Normal;
_mainWindow.BringToFront();
_mainWindow.Activate();
}
/// <summary>Sicherheitsabfrage; bei Bestätigung wird die Message-Loop beendet.</summary>
public void RequestShutdown()
{
if (ShutdownConfirmed) return;
var owner = _mainWindow is { IsDisposed: false } ? _mainWindow : null;
var result = MessageBox.Show(owner,
"IBKRTrader wirklich beenden? Laufende Worker/Services werden gestoppt.",
"Beenden", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning);
if (result != DialogResult.OK) return;
ShutdownConfirmed = true;
Application.Exit();
}
public void OpenView(string viewId)
{
var view = _views.FirstOrDefault(v => v.Id == viewId);
if (view is not null) OpenView(view);
}
private void OpenView(ModuleView view)
{
if (_open.TryGetValue(view.Id, out var existing) && !existing.IsDisposed)
{
if (existing.WindowState == FormWindowState.Minimized)
existing.WindowState = FormWindowState.Normal;
existing.BringToFront();
existing.Activate();
return;
}
var form = view.CreateForm();
if (string.IsNullOrEmpty(form.Text) || form.Text == form.Name)
form.Text = view.Title;
// Gemeinsames „Fenster"-Menü in jedes Fenster injizieren (identische Shell-Chrome).
if (form.MainMenuStrip is null)
AttachWindowMenu(form, view.Id);
_open[view.Id] = form;
form.FormClosed += (_, _) =>
{
_open.Remove(view.Id);
OpenStateChanged?.Invoke();
};
form.Show();
OpenStateChanged?.Invoke();
}
private void AttachWindowMenu(Form form, string currentViewId)
{
var menu = new MenuStrip { Dock = DockStyle.Top, ImageScalingSize = new Size(24, 24) };
WindowMenu.Wire(menu, this, currentViewId);
form.Controls.Add(menu);
form.MainMenuStrip = menu;
}
/// <summary>Schließt alle offenen View-Fenster (beim Herunterfahren).</summary>
public void CloseAllViews()
{
foreach (var form in _open.Values.ToList())
if (!form.IsDisposed)
form.Close();
_open.Clear();
}
}