Files
PolyTraderSharp/Ui/WindowMenu.cs
T
RichardandClaude Opus 5 ad1593bfc1 P1a: PolyTrader.Core auf net8.0 - UI-Contract toolkit-neutral
Der Core traegt keine WinForms-/System.Drawing-Abhaengigkeit mehr und baut als
plattformneutrales net8.0 (verifiziert: publish -r linux-x64 erfolgreich).

- ModuleView.CreateForm (Func<Form>) -> CreateView (Func<object>): die Shell kennt
  ihr Toolkit und castet, der Core nicht. Avalonia kann denselben Contract nutzen.
- ModuleView.Icon (System.Drawing.Image, seit .NET 7 Windows-only) -> IconKey (string).
  Aufloesung Schluessel->Bildressource neu in Ui/ViewIcons.cs, ersetzt Program.AssignMenuIcons.
- WindowMenu.cs (reine WinForms-Logik) aus dem Core nach Ui/ verschoben.
- WindowMenuTests entfernt: testet die eingefrorene WinForms-Menuelogik, die das
  Testprojekt nach dem Core-Schnitt nicht mehr erreicht. Im Tag winforms-final erhalten;
  das Avalonia-Gegenstueck bekommt eigene Tests (P6/P9).

Module und App bleiben vorerst net8.0-windows - ihre UI zieht erst mit der
Avalonia-Portierung um. Windows-App unveraendert lauffaehig (--smoke-ui gruen).

442 Tests gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 17:10:41 +02:00

98 lines
4.7 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 System;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using PolyTrader.Core.Modularity;
namespace PolyTraderSharp.Ui
{
/// <summary>
/// WinForms-Umsetzung des gemeinsamen Fenster-Menüs. Liegt bewusst in der App und nicht mehr
/// im Core: der Core ist seit der Linux-Portierung toolkit-neutral (net8.0) und darf keine
/// WinForms-Typen mehr kennen. Die Avalonia-Shell bekommt später ihr eigenes Gegenstück.
///
/// Baut das gemeinsame Fenster-Menü, das auf JEDEM PolyTrader-Fenster erscheint und das Wechseln
/// zwischen allen Fenstern (Launcher + Core + Module) erlaubt. Da es nur den Core-Contract
/// <see cref="IModuleUiHost"/> nutzt, funktioniert es auch aus Modul-Fenstern (die die App nicht kennen).
///
/// Die Fenster werden NICHT mehr in einem Untermenü „Fenster" versteckt, sondern als eigenständige
/// Einträge (mit Icon) direkt nebeneinander in der obersten Menüleiste aufgelistet. Das aktuelle
/// Fenster ist fett + angehakt, weitere offene Fenster sind angehakt.
/// Rechts liegt eine kontextabhängige Aktion: NUR auf dem Launcher (Hauptprozess) „Beenden"
/// (fährt die gesamte App über die Sicherheitsabfrage herunter); auf allen anderen Fenstern
/// „Fenster schließen" (schließt nur dieses Fenster ohne Einfluss auf laufende Module).
/// Die Leiste wird bei jeder Statusänderung (Fenster geöffnet/geschlossen) neu aufgebaut.
/// </summary>
public static class WindowMenu
{
/// <summary>
/// Verdrahtet eine Designer-<see cref="MenuStrip"/> mit der Fensterliste: füllt sie sofort und
/// baut sie bei jeder Offen-Status-Änderung neu auf. Die Registrierung wird sauber gelöst, sobald
/// die Menüleiste (mit ihrem Fenster) entsorgt wird kein Event-Leak über die Host-Lebensdauer.
/// </summary>
public static void Wire(MenuStrip menu, IModuleUiHost host, string? currentViewId)
{
void Refresh()
{
if (menu.IsDisposed) return;
if (menu.IsHandleCreated && menu.InvokeRequired)
{
try { menu.BeginInvoke((Action)(() => Populate(menu, host, currentViewId))); }
catch { /* Fenster wird gerade geschlossen */ }
return;
}
Populate(menu, host, currentViewId);
}
Populate(menu, host, currentViewId);
host.OpenStateChanged += Refresh;
menu.Disposed += (_, _) => host.OpenStateChanged -= Refresh;
}
/// <summary>Baut die Menüleiste komplett neu auf (Launcher, alle Views nebeneinander, Beenden rechts).</summary>
public static void Populate(MenuStrip menu, IModuleUiHost host, string? currentViewId)
{
menu.Items.Clear();
var launcher = new ToolStripMenuItem("Launcher") { Checked = currentViewId == null };
if (currentViewId == null) launcher.Font = new Font(launcher.Font, FontStyle.Bold);
launcher.Click += (_, _) => host.ActivateMain();
menu.Items.Add(launcher);
foreach (var view in host.Views.OrderBy(v => v.Order).ThenBy(v => v.Title))
{
bool isCurrent = view.Id == currentViewId;
var image = ViewIcons.Resolve(view.IconKey);
var item = new ToolStripMenuItem(view.Title)
{
Image = image,
ImageScaling = ToolStripItemImageScaling.SizeToFit,
DisplayStyle = image != null
? ToolStripItemDisplayStyle.ImageAndText
: ToolStripItemDisplayStyle.Text,
Checked = isCurrent || host.IsOpen(view.Id)
};
if (isCurrent) item.Font = new Font(item.Font, FontStyle.Bold);
string id = view.Id;
item.Click += (_, _) => host.OpenView(id);
menu.Items.Add(item);
}
// Kontextabhängige rechte Aktion: nur der Launcher darf die App beenden (Sicherheitsabfrage);
// jedes andere Fenster bietet nur „Fenster schließen" (kein App-Shutdown, Module laufen weiter).
if (currentViewId == null)
{
var exit = new ToolStripMenuItem("Beenden") { Alignment = ToolStripItemAlignment.Right };
exit.Click += (_, _) => host.RequestShutdown();
menu.Items.Add(exit);
}
else
{
var close = new ToolStripMenuItem("Fenster schließen") { Alignment = ToolStripItemAlignment.Right };
close.Click += (_, _) => menu.FindForm()?.Close();
menu.Items.Add(close);
}
}
}
}