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>
135 lines
5.6 KiB
C#
135 lines
5.6 KiB
C#
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) };
|
||
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;
|
||
}
|
||
|
||
// CreateView liefert bewusst object (toolkit-neutraler Core-Contract). Die WinForms-Shell
|
||
// kennt ihr Toolkit und castet hier; ein falscher Typ ist ein Programmierfehler und soll
|
||
// laut knallen, nicht still ein leeres Fenster erzeugen.
|
||
var form = (Form)view.CreateView();
|
||
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();
|
||
}
|
||
}
|
||
}
|