@
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> @
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using IBKRTrader.Core.Logging;
|
||||
|
||||
namespace IBKRTrader.UI.Views;
|
||||
|
||||
/// <summary>Core-View: Live-Log (RichTextBox), an den LoggingService gebunden.</summary>
|
||||
public sealed class LogsView : Form
|
||||
{
|
||||
public LogsView(LoggingService logger)
|
||||
{
|
||||
Text = "Logs";
|
||||
Width = 1000;
|
||||
Height = 650;
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
|
||||
var rtb = new RichTextBox { Dock = DockStyle.Fill };
|
||||
Controls.Add(rtb);
|
||||
|
||||
// Verbindet den Logger mit der RichTextBox (Theme + AttachRichTextBox).
|
||||
_ = new LogPanelController(rtb, logger);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using IBKRTrader.Core.Settings;
|
||||
|
||||
namespace IBKRTrader.UI.Views;
|
||||
|
||||
/// <summary>Core-View: Einstellungen (PropertyGrid auf AppSettings) mit Speichern-Button.</summary>
|
||||
public sealed class SettingsView : Form
|
||||
{
|
||||
public SettingsView(SettingsService settings)
|
||||
{
|
||||
Text = "Settings";
|
||||
Width = 820;
|
||||
Height = 720;
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
|
||||
var grid = new PropertyGrid { Dock = DockStyle.Fill, SelectedObject = settings.Settings };
|
||||
|
||||
var save = new Button { Text = "Speichern", Dock = DockStyle.Bottom, Height = 36 };
|
||||
save.Click += (_, _) => settings.Save();
|
||||
|
||||
Controls.Add(grid);
|
||||
Controls.Add(save);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using IBKRTrader.Core.Workers;
|
||||
|
||||
namespace IBKRTrader.UI.Views;
|
||||
|
||||
/// <summary>Core-View: Worker/Services-Übersicht (DataGridView, live an die WorkerEngine gebunden).</summary>
|
||||
public sealed class WorkersView : Form
|
||||
{
|
||||
public WorkersView(WorkerEngine engine)
|
||||
{
|
||||
Text = "Workers / Services";
|
||||
Width = 1200;
|
||||
Height = 700;
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
|
||||
var dgv = new DataGridView { Dock = DockStyle.Fill };
|
||||
Controls.Add(dgv);
|
||||
|
||||
WorkerListBindingSource.Setup(dgv, engine.WorkerInfos);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user