Files
IBKRTrader/LauncherForm.cs
T
Richard 6c8e36dc3c @
R3 Slice 3: CongressTrading-Modul + Worker-Log + core_settings auf EF Core

- Modul: CongressTradingDbContext (ct_congressMember/ct_trade) + Design-Time-Factory;
  CongressRepository von Dapper auf EF; CongressTrade.DetailsFetched ergaenzt; Dapper entfernt
- Core: CoreSettingsService (core_settings via EF); WorkerBase-Log auf EF (core_worker_log);
  7 Worker-Ctors DatabaseService -> IDbContextFactory<CoreDbContext>
- CoreMigrations + CongressMigrations (Dapper) entfernt; core_-Schema nun rein EF
- EF-Migration InitialCongressTrading; AddCorePersistence-Fallback fuer leeren Connection-String
  (App startet ohne DB); Connection aus appsettings.Local.json (gitignored)
- Tests: +4 CongressRepository (EF-InMemory) -> 39/39 gruen; Build + smoke-ui + App-Start ok

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

173 lines
6.1 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.Modularity;
using IBKRTrader.Core.Settings;
using IBKRTrader.Core.Workers;
using IBKRTrader.UI;
using Microsoft.Extensions.DependencyInjection;
namespace IBKRTrader;
/// <summary>
/// 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.
/// </summary>
public sealed class LauncherForm : Form
{
private readonly ShellUiHost _uiHost;
private readonly IServiceProvider _services;
private readonly LoggingService _logger;
private readonly WorkerEngine _workerEngine;
private readonly IReadOnlyList<IModule> _modules;
private readonly Dictionary<string, ToolStripButton> _viewButtons = new(StringComparer.OrdinalIgnoreCase);
private readonly ToolStripStatusLabel _status = new("Start...");
public LauncherForm(ShellUiHost uiHost, IServiceProvider services)
{
_uiHost = uiHost;
_services = services;
_logger = services.GetRequiredService<LoggingService>();
_workerEngine = services.GetRequiredService<WorkerEngine>();
_modules = services.GetServices<IModule>().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);
// Fenster-Buttons in einem ToolStrip (Icon über Text) wie im PolytraderSharp-Launcher.
var toolstrip = new ToolStrip
{
Dock = DockStyle.Top,
GripStyle = ToolStripGripStyle.Hidden,
ImageScalingSize = new Size(32, 32),
AutoSize = true,
Padding = new Padding(4)
};
foreach (var view in _uiHost.Views.OrderBy(v => v.Order).ThenBy(v => v.Title))
{
var id = view.Id;
var btn = new ToolStripButton(view.Title, view.Icon)
{
DisplayStyle = ToolStripItemDisplayStyle.ImageAndText,
ImageScaling = ToolStripItemImageScaling.None,
TextImageRelation = TextImageRelation.ImageAboveText,
AutoSize = true,
Padding = new Padding(6, 2, 6, 2)
};
btn.Click += (_, _) => _uiHost.OpenView(id);
_viewButtons[id] = btn;
toolstrip.Items.Add(btn);
}
var content = new Panel { Dock = DockStyle.Fill, BackColor = SystemColors.ControlLightLight };
content.Controls.Add(new Label
{
Text = "IBKRTrader — Launcher\nFenster über die Leiste oben öffnen.",
Dock = DockStyle.Fill, TextAlign = ContentAlignment.MiddleCenter,
ForeColor = SystemColors.GrayText, Font = new Font(Font.FontFamily, 11f)
});
var statusStrip = new StatusStrip();
statusStrip.Items.Add(_status);
// Dock-Stacking: zuletzt hinzugefügtes Top-Control liegt oben → Menü über ToolStrip.
Controls.Add(content);
Controls.Add(statusStrip);
Controls.Add(toolstrip);
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<SettingsService>().Settings.Logging.Level;
if (Enum.TryParse<AppLogLevel>(levelStr, true, out var level))
_logger.SetMinLevel(level);
// core_-Schema läuft über EF-Migrationen (extern angewendet). Nur ibkr_ (Dapper) noch hier.
SetStatus("Migrationen...");
try
{
await _services.GetRequiredService<IBKRMigrations>().RunAsync();
}
catch (Exception ex)
{
_logger.Error("Core", "IBKR-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.Checked = _uiHost.IsOpen(id);
}
private void SetStatus(string text)
{
if (IsDisposed) return;
if (InvokeRequired) { BeginInvoke((Action)(() => SetStatus(text))); return; }
_status.Text = $"Status: {text} | {DateTime.Now:HH:mm:ss}";
}
}