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> @
225 lines
7.4 KiB
C#
225 lines
7.4 KiB
C#
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}";
|
||
}
|
||
}
|
||
}
|