using IBKRTrader.Core.Database.Migrations; using IBKRTrader.Core.Logging; using IBKRTrader.Core.Modularity; using IBKRTrader.Core.Settings; using IBKRTrader.Core.Workers; using IBKRTrader.UI; using Microsoft.Extensions.DependencyInjection; namespace IBKRTrader; /// /// Launcher – das Basis-Fenster (Shell). Zeigt je registrierter View einen Button, führt beim Start /// Migrationen aus, startet Module und die WorkerEngine, und trägt das gemeinsame Fenster-Menü. /// Die inhaltlichen Ansichten (Logs, Settings, Workers, Module) sind eigenständige Fenster. /// public sealed class LauncherForm : Form { private readonly ShellUiHost _uiHost; private readonly IServiceProvider _services; private readonly LoggingService _logger; private readonly WorkerEngine _workerEngine; private readonly IReadOnlyList _modules; private readonly Dictionary _viewButtons = new(StringComparer.OrdinalIgnoreCase); private readonly FlowLayoutPanel _buttonPanel = new() { Dock = DockStyle.Fill, Padding = new Padding(16), AutoScroll = true }; private readonly ToolStripStatusLabel _status = new("Start..."); public LauncherForm(ShellUiHost uiHost, IServiceProvider services) { _uiHost = uiHost; _services = services; _logger = services.GetRequiredService(); _workerEngine = services.GetRequiredService(); _modules = services.GetServices().ToList(); Text = "IBKRTrader — Launcher"; Width = 720; Height = 540; StartPosition = FormStartPosition.CenterScreen; _uiHost.SetMainWindow(this); BuildUi(); _uiHost.OpenStateChanged += UpdateButtonStates; } private void BuildUi() { var menu = new MenuStrip { Dock = DockStyle.Top, ImageScalingSize = new Size(24, 24) }; WindowMenu.Wire(menu, _uiHost, null); foreach (var view in _uiHost.Views.OrderBy(v => v.Order).ThenBy(v => v.Title)) { var id = view.Id; var btn = new Button { Text = view.Title, Width = 210, Height = 48, Margin = new Padding(8), TextAlign = ContentAlignment.MiddleLeft, Image = view.Icon, ImageAlign = ContentAlignment.MiddleLeft, TextImageRelation = TextImageRelation.ImageBeforeText }; btn.Click += (_, _) => _uiHost.OpenView(id); _viewButtons[id] = btn; _buttonPanel.Controls.Add(btn); } var statusStrip = new StatusStrip(); statusStrip.Items.Add(_status); // Reihenfolge: Fill-Panel zuerst, dann Bottom, dann Top (WinForms Dock-Stacking). Controls.Add(_buttonPanel); Controls.Add(statusStrip); Controls.Add(menu); MainMenuStrip = menu; } protected override void OnLoad(EventArgs e) { base.OnLoad(e); _ = StartupAsync(); } protected override void OnFormClosing(FormClosingEventArgs e) { // Auch das Schließen-X läuft über die Sicherheitsabfrage. if (!_uiHost.ShutdownConfirmed) { e.Cancel = true; BeginInvoke((Action)(() => _uiHost.RequestShutdown())); return; } _uiHost.CloseAllViews(); try { foreach (var module in _modules) module.StopAsync(default).GetAwaiter().GetResult(); } catch { /* Shutdown darf nicht am Modul scheitern */ } _workerEngine.StopAllAsync().GetAwaiter().GetResult(); base.OnFormClosing(e); } private async Task StartupAsync() { _logger.Info("Core", "=== IBKRTrader startet ==="); _logger.Info("Core", $"Version: 1.0.0 | .NET {Environment.Version}"); // Log-Level aus Settings. var levelStr = _services.GetRequiredService().Settings.Logging.Level; if (Enum.TryParse(levelStr, true, out var level)) _logger.SetMinLevel(level); SetStatus("Migrationen..."); try { await _services.GetRequiredService().RunAsync(); await _services.GetRequiredService().RunAsync(); } catch (Exception ex) { _logger.Error("Core", "Core-Datenbankfehler beim Start.", ex); } foreach (var module in _modules) { try { await module.StartAsync(default); } catch (Exception ex) { _logger.Error(module.Name, $"{module.Name}: Start fehlgeschlagen.", ex); } } await _workerEngine.StartAllAsync(); _logger.Info("Core", "IBKRTrader bereit."); SetStatus("Bereit"); UpdateButtonStates(); } private void UpdateButtonStates() { if (IsDisposed) return; if (InvokeRequired) { BeginInvoke((Action)UpdateButtonStates); return; } foreach (var (id, btn) in _viewButtons) btn.Font = new Font(btn.Font, _uiHost.IsOpen(id) ? FontStyle.Bold : FontStyle.Regular); } private void SetStatus(string text) { if (IsDisposed) return; if (InvokeRequired) { BeginInvoke((Action)(() => SetStatus(text))); return; } _status.Text = $"Status: {text} | {DateTime.Now:HH:mm:ss}"; } }