From 25f0d34042b0ed34b6b9696a1e73878777c227d1 Mon Sep 17 00:00:00 2001 From: Richard Date: Mon, 20 Jul 2026 18:34:45 +0200 Subject: [PATCH] UI Slice 1b: Fenster-Menue auf ALLEN Fenstern (zentrale Shell-Injektion) ShellUiHost injiziert das gemeinsame 'Fenster'-Menue beim Oeffnen zentral in jedes Fenster (nur wenn keins eigenes vorhanden ist, z.B. Launcher). Damit erscheint das Menue auf allen Core- und Modul-Fenstern ohne Achtfach-Designer-Duplikat, und jedes kuenftige Fenster bekommt es automatisch. Inhaltliche Controls bleiben designerbasiert; das Nav-Menue ist Shell-Chrome. Tests: +3 (WindowMenu.Populate: Inhalt Launcher/Views/Beenden, aktuelles Fenster fett+angehakt, offenes angehakt, Klick navigiert). 389 Tests gruen, --smoke-ui gruen. Co-Authored-By: Claude Opus 4.8 --- Ui/ShellUiHost.cs | 20 ++++++ tests/PolyTrader.Tests/WindowMenuTests.cs | 84 +++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 tests/PolyTrader.Tests/WindowMenuTests.cs diff --git a/Ui/ShellUiHost.cs b/Ui/ShellUiHost.cs index eae185d..cc888b1 100644 --- a/Ui/ShellUiHost.cs +++ b/Ui/ShellUiHost.cs @@ -28,6 +28,20 @@ namespace PolyTraderSharp.Ui /// Setzt das Hauptfenster (Launcher) – Ziel für . public void SetMainWindow(Form main) => _mainWindow = main; + /// + /// Fügt einem Fenster das gemeinsame „Fenster"-Menü hinzu (oberste Leiste). Als letztes + /// hinzugefügtes Top-Control belegt der MenuStrip die oberste Zeile über etwaigen ToolStrips. + /// + private void AttachWindowMenu(Form form, string currentViewId) + { + var menu = new MenuStrip { Dock = DockStyle.Top }; + var fenster = new ToolStripMenuItem("Fenster"); + menu.Items.Add(fenster); + PolyTrader.Core.Modularity.WindowMenu.Wire(fenster, this, currentViewId); + form.Controls.Add(menu); + form.MainMenuStrip = menu; + } + public void ActivateMain() { if (_mainWindow == null || _mainWindow.IsDisposed) return; @@ -61,6 +75,12 @@ namespace PolyTraderSharp.Ui if (string.IsNullOrEmpty(form.Text) || form.Text == form.Name) form.Text = view.Title; + // Gemeinsames „Fenster"-Menü zentral in jedes Fenster injizieren (identische Shell-Chrome + // auf allen Fenstern; die inhaltlichen Controls bleiben designerbasiert). Nur, wenn das + // Fenster nicht bereits ein eigenes Menü mitbringt (z. B. der Launcher). + if (form.MainMenuStrip == null) + AttachWindowMenu(form, view.Id); + // Alle Fenster maximiert (Vorgabe): Full-HD-Ziel, skaliert auf größere Auflösungen. form.StartPosition = FormStartPosition.CenterScreen; form.WindowState = FormWindowState.Maximized; diff --git a/tests/PolyTrader.Tests/WindowMenuTests.cs b/tests/PolyTrader.Tests/WindowMenuTests.cs new file mode 100644 index 0000000..236f6b5 --- /dev/null +++ b/tests/PolyTrader.Tests/WindowMenuTests.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Forms; +using PolyTrader.Core.Modularity; +using Xunit; + +namespace PolyTrader.Tests +{ + /// Sicherheitsnetz für das gemeinsame „Fenster"-Menü (Slice 1): Inhalt + Verhalten. + public class WindowMenuTests + { + private sealed class FakeUiHost : IModuleUiHost + { + public List ViewList { get; } = new(); + public HashSet OpenIds { get; } = new(); + public List Opened { get; } = new(); + public bool MainActivated { get; private set; } + + public IReadOnlyList Views => ViewList; + public bool IsOpen(string viewId) => OpenIds.Contains(viewId); + public void OpenView(string viewId) => Opened.Add(viewId); + public void ActivateMain() => MainActivated = true; + public void RegisterView(ModuleView view) => ViewList.Add(view); + public event Action? OpenStateChanged { add { } remove { } } + } + + private static FakeUiHost Host() + { + var h = new FakeUiHost(); + h.RegisterView(new ModuleView { Id = "core.dashboard", Title = "Dashboard", Order = 1 }); + h.RegisterView(new ModuleView { Id = "core.terminal", Title = "Terminal / Logs", Order = 2 }); + h.OpenIds.Add("core.terminal"); // Terminal ist offen + return h; + } + + [Fact] + public void Populate_lists_launcher_views_and_exit() + { + var host = Host(); + var fenster = new ToolStripMenuItem("Fenster"); + + WindowMenu.Populate(fenster, host, currentViewId: "core.dashboard"); + + var texts = fenster.DropDownItems.OfType().Select(i => i.Text).ToList(); + Assert.Equal("Launcher", texts[0]); + Assert.Contains("Dashboard", texts); + Assert.Contains("Terminal / Logs", texts); + Assert.Contains("Beenden", texts); + } + + [Fact] + public void Current_view_is_bold_and_checked_open_view_is_checked() + { + var host = Host(); + var fenster = new ToolStripMenuItem("Fenster"); + + WindowMenu.Populate(fenster, host, currentViewId: "core.dashboard"); + + var dashboard = fenster.DropDownItems.OfType().First(i => i.Text == "Dashboard"); + var terminal = fenster.DropDownItems.OfType().First(i => i.Text == "Terminal / Logs"); + + Assert.True(dashboard.Checked); // aktuelles Fenster + Assert.True(dashboard.Font.Bold); + Assert.True(terminal.Checked); // offenes Fenster + Assert.False(terminal.Font.Bold); + } + + [Fact] + public void Clicking_items_navigates() + { + var host = Host(); + var fenster = new ToolStripMenuItem("Fenster"); + WindowMenu.Populate(fenster, host, currentViewId: null); + + var items = fenster.DropDownItems.OfType().ToList(); + items.First(i => i.Text == "Launcher").PerformClick(); + items.First(i => i.Text == "Terminal / Logs").PerformClick(); + + Assert.True(host.MainActivated); + Assert.Contains("core.terminal", host.Opened); + } + } +}