Files
IBKRTrader/LauncherForm.cs
T
Richard d0bc833235 @
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>
@
2026-07-27 10:39:50 +02:00

225 lines
7.4 KiB
C#
Raw 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.Database.Migrations;
using IBKRTrader.Core.Logging;
using IBKRTrader.Core.Modules;
using IBKRTrader.Core.Settings;
using IBKRTrader.Core.Workers;
using IBKRTrader.UI;
namespace IBKRTrader;
/// <summary>
/// Launcher das Basis-Fenster der Anwendung.
/// Enthält die Core-Panels (Workers, Logs, Settings) und eine Modul-Liste,
/// aus der jedes Modul als eigenständiges Fenster geöffnet wird.
/// </summary>
public partial class LauncherForm : Form
{
private readonly LoggingService _logger;
private readonly WorkerEngine _workerEngine;
private readonly SettingsService _settings;
private readonly CoreMigrations _migrations;
private readonly IBKRMigrations _ibkrMigrations;
private readonly ModuleRegistry _modules;
private readonly WindowManager _windows;
private readonly IServiceProvider _services;
private LogPanelController? _logPanel;
public LauncherForm(
LoggingService logger,
WorkerEngine workerEngine,
SettingsService settings,
CoreMigrations migrations,
IBKRMigrations ibkrMigrations,
ModuleRegistry modules,
WindowManager windows,
IServiceProvider services)
{
InitializeComponent();
_logger = logger;
_workerEngine = workerEngine;
_settings = settings;
_migrations = migrations;
_ibkrMigrations = ibkrMigrations;
_modules = modules;
_windows = windows;
_services = services;
}
// ─── Form-Events ──────────────────────────────────────────────────────────
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
InitializeLogPanel();
InitializeWorkerList();
InitializeSettingsGrid();
InitializeModulePanel();
_ = StartupAsync();
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosing(e);
// Offene Modul-Fenster schließen, dann Worker sauber beenden.
_windows.CloseAll();
_workerEngine.StopAllAsync().GetAwaiter().GetResult();
}
// ─── Initialisierung ──────────────────────────────────────────────────────
private void InitializeLogPanel()
{
// RichTextBox auf Dock.Fill setzen
rtb_logs.Dock = DockStyle.Fill;
_logPanel = new LogPanelController(rtb_logs, _logger);
// LogLevel aus Settings setzen
var levelStr = _settings.Settings.Logging.Level;
if (Enum.TryParse<AppLogLevel>(levelStr, true, out var level))
_logger.SetMinLevel(level);
}
private void InitializeWorkerList()
{
WorkerListBindingSource.Setup(dgv_workerlist, _workerEngine.WorkerInfos);
}
private void InitializeSettingsGrid()
{
pg_settings.SelectedObject = _settings.Settings;
}
/// <summary>Baut je Modul eine Karte mit „Fenster öffnen"-Button.</summary>
private void InitializeModulePanel()
{
flp_modules.FlowDirection = FlowDirection.LeftToRight;
flp_modules.WrapContents = true;
flp_modules.Controls.Clear();
foreach (var module in _modules.Modules)
flp_modules.Controls.Add(BuildModuleCard(module));
}
private Control BuildModuleCard(IModule module)
{
var card = new Panel
{
Width = 320,
Height = 150,
Margin = new Padding(8),
BorderStyle = BorderStyle.FixedSingle
};
var title = new Label
{
Text = $"{module.DisplayName} [{module.Key}]",
Font = new Font(Font.FontFamily, 11f, FontStyle.Bold),
Location = new Point(10, 10),
AutoSize = true
};
var desc = new Label
{
Text = module.Description,
Location = new Point(10, 42),
Size = new Size(300, 60),
AutoEllipsis = true
};
var open = new Button
{
Text = "Fenster öffnen",
Location = new Point(10, 108),
Width = 140,
Tag = module
};
open.Click += (_, _) => OpenModuleWindow(module);
var version = new Label
{
Text = $"v{module.Version}",
Location = new Point(240, 113),
AutoSize = true,
ForeColor = SystemColors.GrayText
};
card.Controls.Add(title);
card.Controls.Add(desc);
card.Controls.Add(open);
card.Controls.Add(version);
return card;
}
/// <summary>Öffnet (oder fokussiert) das eigenständige Fenster eines Moduls.</summary>
private void OpenModuleWindow(IModule module)
{
try
{
_windows.OpenOrFocus(module.Key, () => module.CreateWindow(_services));
}
catch (Exception ex)
{
_logger.Error(module.Key, $"{module.DisplayName}: Fenster konnte nicht geöffnet werden.", ex);
MessageBox.Show(this,
$"Fenster für '{module.DisplayName}' konnte nicht geöffnet werden:\n{ex.Message}",
"Modul-Fenster", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
// ─── Async Startup ────────────────────────────────────────────────────────
private async Task StartupAsync()
{
_logger.Info("Core", "=== IBKRTrader startet ===");
_logger.Info("Core", $"Version: 1.0.0 | .NET {Environment.Version}");
// DB-Verbindung testen und Core-Migrationen laufen lassen
try
{
_logger.Info("Core", "Verbinde mit Datenbank...");
await _migrations.RunAsync();
await _ibkrMigrations.RunAsync();
}
catch (Exception ex)
{
_logger.Error("Core", "Core-Datenbankfehler beim Start.", ex);
}
// Modul-Migrationen: über die Registry iterieren (Core kennt kein Modul).
foreach (var module in _modules.Modules)
{
try
{
await module.InitializeAsync(_services);
}
catch (Exception ex)
{
_logger.Error(module.Key, $"{module.DisplayName}: Initialisierung fehlgeschlagen.", ex);
}
}
// Worker starten
await _workerEngine.StartAllAsync();
_logger.Info("Core", "IBKRTrader bereit.");
UpdateStatusBar("Bereit");
}
// ─── StatusBar ────────────────────────────────────────────────────────────
private void UpdateStatusBar(string text)
{
if (statusStrip1.InvokeRequired)
statusStrip1.BeginInvoke(() => UpdateStatusBar(text));
else
{
// Vorhandenes Label nutzen oder neues anlegen
if (statusStrip1.Items.Count == 0)
statusStrip1.Items.Add(new ToolStripStatusLabel());
statusStrip1.Items[0].Text = $"Status: {text} | {DateTime.Now:HH:mm:ss}";
}
}
}