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>
This commit is contained in:
@@ -1,21 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace PolyTrader.Core.Modularity
|
||||
{
|
||||
/// <summary>
|
||||
/// Eine vom Core oder einem Modul beigesteuerte Fenster-Ansicht. Die eigentliche UI ist
|
||||
/// ein designbares <see cref="Form"/>, das über <see cref="CreateForm"/> erzeugt wird
|
||||
/// (mit DI-Abhängigkeiten). Die Shell zeigt je View höchstens eine Instanz und holt ein
|
||||
/// bereits offenes Fenster wieder in den Vordergrund.
|
||||
/// Eine vom Core oder einem Modul beigesteuerte Fenster-Ansicht. Die Shell zeigt je View
|
||||
/// höchstens eine Instanz und holt ein bereits offenes Fenster wieder in den Vordergrund.
|
||||
///
|
||||
/// <para><b>Bewusst toolkit-neutral:</b> <see cref="CreateView"/> liefert ein <see cref="object"/>,
|
||||
/// keinen konkreten Fenstertyp. Dadurch bleibt der Core plattformunabhängig (<c>net8.0</c>) und
|
||||
/// trägt keine WinForms-Abhängigkeit mehr. Die jeweilige Shell kennt ihr Toolkit und castet:
|
||||
/// die WinForms-Shell auf <c>System.Windows.Forms.Form</c>, die künftige Avalonia-Shell auf
|
||||
/// <c>Avalonia.Controls.Window</c>. Module liefern das Fenster, die Shell hängt es auf.</para>
|
||||
/// </summary>
|
||||
public sealed class ModuleView
|
||||
{
|
||||
/// <summary>Stabile ID für Einzelinstanz-Handling (nur ein Fenster je View).</summary>
|
||||
public string Id { get; init; } = Guid.NewGuid().ToString();
|
||||
|
||||
/// <summary>Titel (Fallback-Fenstertitel, falls das Form keinen eigenen setzt).</summary>
|
||||
/// <summary>Titel (Fallback-Fenstertitel, falls das Fenster keinen eigenen setzt).</summary>
|
||||
public string Title { get; init; } = "Fenster";
|
||||
|
||||
/// <summary>Optionale Gruppierung (z.B. "Core", "CopyTrading").</summary>
|
||||
@@ -25,21 +28,26 @@ namespace PolyTrader.Core.Modularity
|
||||
public int Order { get; init; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Optionales Icon für Menü/Buttons. Wird von der Shell/den Menüs genutzt. Settable, damit die
|
||||
/// App den von Modulen registrierten Views zentral ein Icon aus ihren Ressourcen zuweisen kann
|
||||
/// Logischer Schlüssel des Symbols für Menüs/Buttons (z.B. "dashboard", "money"). Die Shell
|
||||
/// löst ihn gegen ihre eigenen Ressourcen auf – der Core kennt keine Bilddaten mehr (früher
|
||||
/// <c>System.Drawing.Image</c>, das seit .NET 7 Windows-only ist und auf Linux wirft).
|
||||
/// Settable, damit die App den von Modulen registrierten Views zentral ein Symbol zuweisen kann
|
||||
/// (Module referenzieren die App-Ressourcen nicht).
|
||||
/// </summary>
|
||||
public System.Drawing.Image? Icon { get; set; }
|
||||
public string? IconKey { get; set; }
|
||||
|
||||
/// <summary>Erzeugt das anzuzeigende Fenster (frische Instanz je Öffnung).</summary>
|
||||
public Func<Form> CreateForm { get; init; } = () => new Form();
|
||||
/// <summary>
|
||||
/// Erzeugt das anzuzeigende Fenster (frische Instanz je Öffnung). Rückgabetyp ist
|
||||
/// <see cref="object"/> – siehe Klassen-Doku zur Toolkit-Neutralität.
|
||||
/// </summary>
|
||||
public Func<object> CreateView { get; init; } = () => new object();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wird der Shell beim Start übergeben; Core und Module registrieren hier ihre Ansichten.
|
||||
/// Die Shell (Launcher) öffnet die Fenster auf Anforderung und verwaltet den Offen-Status.
|
||||
/// Über die Navigations-Mitglieder kann JEDES Fenster (auch Modul-Fenster, die nur den Core
|
||||
/// kennen) das gemeinsame „Fenster"-Menü bauen (siehe <see cref="WindowMenu"/>).
|
||||
/// kennen) das gemeinsame „Fenster"-Menü bauen.
|
||||
/// </summary>
|
||||
public interface IModuleUiHost
|
||||
{
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<!-- Linux-Portierung P1: Der Core ist plattformneutral. Der UI-Contract (ModuleView/IModuleUiHost)
|
||||
ist toolkit-neutral (Func<object> statt Func<Form>, IconKey statt System.Drawing.Image),
|
||||
damit hier weder WinForms noch System.Drawing.Common hängen – letzteres ist seit .NET 7
|
||||
Windows-only und wirft auf Linux. Die WinForms-Umsetzung liegt in PolyTrader.App (Ui/). -->
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<!-- Der Core stellt den UI-Contract (IModuleUiHost/ModuleView mit System.Windows.Forms.Control)
|
||||
bereit, damit Module designbare UserControls beitragen können. -->
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Threading;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@@ -57,7 +57,7 @@ namespace PolyTrader.Modules.Accounting
|
||||
Title = "Accounting",
|
||||
Group = "Accounting",
|
||||
Order = 400,
|
||||
CreateForm = () =>
|
||||
CreateView = () =>
|
||||
{
|
||||
var form = new Ui.AccountingMainForm();
|
||||
form.Initialize(services);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Channels;
|
||||
@@ -92,7 +92,7 @@ namespace PolyTrader.Modules.CopyTrading
|
||||
Title = "Copytrading",
|
||||
Group = "CopyTrading",
|
||||
Order = 100,
|
||||
CreateForm = () =>
|
||||
CreateView = () =>
|
||||
{
|
||||
var form = new CopyTradingMainForm();
|
||||
form.Initialize(services);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Threading;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@@ -63,7 +63,7 @@ namespace PolyTrader.Modules.ResolutionFarming
|
||||
Title = "ResolutionFarming",
|
||||
Group = "ResolutionFarming",
|
||||
Order = 200,
|
||||
CreateForm = () =>
|
||||
CreateView = () =>
|
||||
{
|
||||
var form = new Ui.ResolutionFarmingMainForm();
|
||||
form.Initialize(services);
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace PolyTrader.Modules.Supervisor
|
||||
Title = "Supervisor",
|
||||
Group = "Supervisor",
|
||||
Order = 300,
|
||||
CreateForm = () =>
|
||||
CreateView = () =>
|
||||
{
|
||||
var form = new Ui.SupervisorMainForm();
|
||||
form.Initialize(services);
|
||||
|
||||
Reference in New Issue
Block a user