Files
PolyTraderSharp/src/PolyTrader.Core/Modularity/WindowMenu.cs
T
RichardandClaude Opus 4.8 039bc240f8 UI Slice 5: Menueleiste (Fenster nebeneinander), sicheres Beenden, Modul-Aktivierung
- Fenster-Menue: WindowMenu fuellt die oberste MenuStrip mit Top-Level-Eintraegen
  nebeneinander (mit Icon) statt Untermenue "Fenster"; ModuleView.Icon zentral in der
  App zugewiesen (AssignMenuIcons). Kein miFenster mehr im Launcher-Designer.
- Sicheres Beenden: ShutdownConfirmDialog (10s-Timer sperrt "Jetzt beenden", Abbrechen
  jederzeit) via IModuleUiHost.RequestShutdown(). Nur der Launcher (Hauptprozess) bietet
  "Beenden"; andere Fenster nur "Fenster schliessen" (kein App-Shutdown, Module laufen
  weiter). Launcher-Schliessen-X routet ueber dieselbe Abfrage.
- Modul-Aktivierung (restart-basiert): ServerSettings.DisabledModules, in Program.Main
  vor der DI-Registrierung gefiltert -> deaktivierte Module werden nicht geladen.
  Dashboard-Tab "Module" zeigt Status + schaltet um (Hinweis: greift nach Neustart).
  Optionaler IPolyTraderModule.GetActivationBlocker fuer "nicht aktivierbar"-Info.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 18:46:09 +02:00

92 lines
4.4 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;
namespace PolyTrader.Core.Modularity
{
/// <summary>
/// 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 item = new ToolStripMenuItem(view.Title)
{
Image = view.Icon,
ImageScaling = ToolStripItemImageScaling.SizeToFit,
DisplayStyle = view.Icon != 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);
}
}
}
}