Files
PolyTraderSharp/Ui/ShellUiHost.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

132 lines
5.4 KiB
C#
Raw Permalink 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.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using PolyTrader.Core.Modularity;
namespace PolyTraderSharp.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).
/// </summary>
public class ShellUiHost : IModuleUiHost
{
private readonly List<ModuleView> _views = new();
private readonly Dictionary<string, Form> _open = new();
private Form? _mainWindow;
private bool _shutdownDialogOpen;
/// <summary>
/// True, sobald das Herunterfahren über die Sicherheitsabfrage bestätigt wurde. Der Launcher
/// wertet das in FormClosing aus, um beim Schließen-X denselben Dialog zu erzwingen.
/// </summary>
public bool ShutdownConfirmed { get; private set; }
/// <summary>Wird ausgelöst, wenn sich der Offen-Status irgendeiner View ändert.</summary>
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;
/// <summary>
/// Fügt einem Fenster die gemeinsame Fenster-Menüleiste hinzu (oberste Leiste, alle Fenster
/// nebeneinander mit Icon). Als letztes hinzugefügtes Top-Control belegt der MenuStrip die
/// oberste Zeile über etwaigen ToolStrips.
/// </summary>
private void AttachWindowMenu(Form form, string currentViewId)
{
var menu = new MenuStrip { Dock = DockStyle.Top, ImageScalingSize = new System.Drawing.Size(24, 24) };
PolyTrader.Core.Modularity.WindowMenu.Wire(menu, this, currentViewId);
form.Controls.Add(menu);
form.MainMenuStrip = menu;
}
public void ActivateMain()
{
if (_mainWindow == null || _mainWindow.IsDisposed) return;
if (_mainWindow.WindowState == FormWindowState.Minimized)
_mainWindow.WindowState = FormWindowState.Maximized;
_mainWindow.BringToFront();
_mainWindow.Activate();
}
public bool IsOpen(string viewId) =>
_open.TryGetValue(viewId, out var form) && !form.IsDisposed;
/// <summary>
/// Zeigt die Sicherheitsabfrage (Timer-gesperrter Beenden-Button, Abbrechen jederzeit). Bei
/// Bestätigung wird die Message-Loop beendet das geordnete Herunterfahren der Module läuft
/// anschließend in Program.Main über <c>AppHost.StopAsync()</c> (nach <c>Application.Run</c>).
/// Aus jedem Fenster aufrufbar; der Dialog erscheint zentriert über dem Hauptfenster.
/// </summary>
public void RequestShutdown()
{
if (ShutdownConfirmed || _shutdownDialogOpen) return;
_shutdownDialogOpen = true;
try
{
using var dlg = new ShutdownConfirmDialog();
var owner = _mainWindow != null && !_mainWindow.IsDisposed ? _mainWindow : null;
var result = owner != null ? dlg.ShowDialog(owner) : dlg.ShowDialog();
if (result != DialogResult.OK) return;
ShutdownConfirmed = true;
Application.Exit(); // beendet die Message-Loop; StopAsync() fährt Module danach sauber herunter
}
finally
{
_shutdownDialogOpen = false;
}
}
public void OpenView(string viewId)
{
var view = _views.FirstOrDefault(v => v.Id == viewId);
if (view != null) OpenView(view);
}
public 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ü 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;
_open[view.Id] = form;
form.FormClosed += (_, _) =>
{
_open.Remove(view.Id);
OpenStateChanged?.Invoke();
};
form.Show();
OpenStateChanged?.Invoke();
}
}
}