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:
+9
-23
@@ -215,7 +215,7 @@ internal static class Program
|
|||||||
Title = "Terminal",
|
Title = "Terminal",
|
||||||
Group = "Core",
|
Group = "Core",
|
||||||
Order = 10,
|
Order = 10,
|
||||||
CreateForm = () =>
|
CreateView = () =>
|
||||||
{
|
{
|
||||||
var view = new PolyTraderSharp.Ui.Views.TerminalView();
|
var view = new PolyTraderSharp.Ui.Views.TerminalView();
|
||||||
view.Initialize(viewServices.GetRequiredService<TerminalLogger>());
|
view.Initialize(viewServices.GetRequiredService<TerminalLogger>());
|
||||||
@@ -228,7 +228,7 @@ internal static class Program
|
|||||||
Title = "Server Settings",
|
Title = "Server Settings",
|
||||||
Group = "Core",
|
Group = "Core",
|
||||||
Order = 20,
|
Order = 20,
|
||||||
CreateForm = () =>
|
CreateView = () =>
|
||||||
{
|
{
|
||||||
var view = new PolyTraderSharp.Ui.Views.SettingsView();
|
var view = new PolyTraderSharp.Ui.Views.SettingsView();
|
||||||
view.Initialize(
|
view.Initialize(
|
||||||
@@ -246,7 +246,7 @@ internal static class Program
|
|||||||
Title = "Server Jobs",
|
Title = "Server Jobs",
|
||||||
Group = "Core",
|
Group = "Core",
|
||||||
Order = 30,
|
Order = 30,
|
||||||
CreateForm = () =>
|
CreateView = () =>
|
||||||
{
|
{
|
||||||
var view = new PolyTraderSharp.Ui.Views.JobsView();
|
var view = new PolyTraderSharp.Ui.Views.JobsView();
|
||||||
view.Initialize(viewServices.GetRequiredService<JobManager>());
|
view.Initialize(viewServices.GetRequiredService<JobManager>());
|
||||||
@@ -259,7 +259,7 @@ internal static class Program
|
|||||||
Title = "Dashboard",
|
Title = "Dashboard",
|
||||||
Group = "Core",
|
Group = "Core",
|
||||||
Order = 5,
|
Order = 5,
|
||||||
CreateForm = () =>
|
CreateView = () =>
|
||||||
{
|
{
|
||||||
var view = new PolyTraderSharp.Ui.Views.DashboardView();
|
var view = new PolyTraderSharp.Ui.Views.DashboardView();
|
||||||
var config = viewServices.GetRequiredService<Microsoft.Extensions.Configuration.IConfiguration>();
|
var config = viewServices.GetRequiredService<Microsoft.Extensions.Configuration.IConfiguration>();
|
||||||
@@ -292,26 +292,12 @@ internal static class Program
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Weist den registrierten Views (Core + Module) ihr Menü-Icon aus den App-Ressourcen zu –
|
/// Weist den registrierten Views (Core + Module) ihren Standard-Symbolschlüssel zu.
|
||||||
/// gemappt über die stabile View-ID. Bereits gesetzte Icons bleiben erhalten.
|
/// Die Auflösung Schlüssel → Bildressource liegt in <see cref="PolyTraderSharp.Ui.ViewIcons"/>;
|
||||||
|
/// Core und Module kennen nur noch den Schlüssel (toolkit-neutral, siehe ModuleView.IconKey).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void AssignMenuIcons(PolyTraderSharp.Ui.ShellUiHost uiHost)
|
private static void AssignMenuIcons(PolyTraderSharp.Ui.ShellUiHost uiHost)
|
||||||
{
|
=> PolyTraderSharp.Ui.ViewIcons.AssignDefaults(uiHost);
|
||||||
var map = new System.Collections.Generic.Dictionary<string, System.Drawing.Image>
|
|
||||||
{
|
|
||||||
["core.dashboard"] = Properties.Resources.dashboard,
|
|
||||||
["core.settings"] = Properties.Resources.setting_tools,
|
|
||||||
["core.terminal"] = Properties.Resources.error_log,
|
|
||||||
["core.jobs"] = Properties.Resources.system_time,
|
|
||||||
["accounting.main"] = Properties.Resources.coins_in_hand,
|
|
||||||
["copytrading.main"] = Properties.Resources.cross_reference,
|
|
||||||
["resolutionfarming.main"] = Properties.Resources.file_start_workflow,
|
|
||||||
["supervisor.main"] = Properties.Resources.emotion_batman,
|
|
||||||
};
|
|
||||||
foreach (var view in uiHost.Views)
|
|
||||||
if (view.Icon == null && map.TryGetValue(view.Id, out var img))
|
|
||||||
view.Icon = img;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RunConfigMigrationFromJson(string folder)
|
private static void RunConfigMigrationFromJson(string folder)
|
||||||
{
|
{
|
||||||
@@ -510,7 +496,7 @@ internal static class Program
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var form = view.CreateForm();
|
using var form = (System.Windows.Forms.Form)view.CreateView();
|
||||||
Console.WriteLine($"[OK] {view.Id} ({view.Title})");
|
Console.WriteLine($"[OK] {view.Id} ({view.Title})");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -62,7 +62,7 @@ namespace PolyTraderSharp.Ui
|
|||||||
|
|
||||||
// Gemeinsame Fenster-Menüleiste: alle Fenster nebeneinander mit Icon (Launcher ist das
|
// Gemeinsame Fenster-Menüleiste: alle Fenster nebeneinander mit Icon (Launcher ist das
|
||||||
// aktuelle Fenster → currentViewId null).
|
// aktuelle Fenster → currentViewId null).
|
||||||
PolyTrader.Core.Modularity.WindowMenu.Wire(menuStrip, _uiHost, null);
|
WindowMenu.Wire(menuStrip, _uiHost, null);
|
||||||
|
|
||||||
btn_liveTrading.Click += (_, _) => CycleLiveTrading();
|
btn_liveTrading.Click += (_, _) => CycleLiveTrading();
|
||||||
btn_demoTrading.Click += (_, _) => CycleDemoTrading();
|
btn_demoTrading.Click += (_, _) => CycleDemoTrading();
|
||||||
|
|||||||
+6
-3
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
@@ -43,7 +43,7 @@ namespace PolyTraderSharp.Ui
|
|||||||
private void AttachWindowMenu(Form form, string currentViewId)
|
private void AttachWindowMenu(Form form, string currentViewId)
|
||||||
{
|
{
|
||||||
var menu = new MenuStrip { Dock = DockStyle.Top, ImageScalingSize = new System.Drawing.Size(24, 24) };
|
var menu = new MenuStrip { Dock = DockStyle.Top, ImageScalingSize = new System.Drawing.Size(24, 24) };
|
||||||
PolyTrader.Core.Modularity.WindowMenu.Wire(menu, this, currentViewId);
|
WindowMenu.Wire(menu, this, currentViewId);
|
||||||
form.Controls.Add(menu);
|
form.Controls.Add(menu);
|
||||||
form.MainMenuStrip = menu;
|
form.MainMenuStrip = menu;
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,10 @@ namespace PolyTraderSharp.Ui
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var form = view.CreateForm();
|
// 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)
|
if (string.IsNullOrEmpty(form.Text) || form.Text == form.Name)
|
||||||
form.Text = view.Title;
|
form.Text = view.Title;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using PolyTrader.Core.Modularity;
|
||||||
|
|
||||||
|
namespace PolyTraderSharp.Ui
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Löst die toolkit-neutralen <see cref="ModuleView.IconKey"/>-Schlüssel gegen die WinForms-
|
||||||
|
/// Bildressourcen der App auf. Diese Zuordnung ist die einzige Stelle, an der Views und
|
||||||
|
/// Bilddaten zusammenkommen – Core und Module kennen nur noch den Schlüssel als Zeichenkette.
|
||||||
|
///
|
||||||
|
/// Die Avalonia-Shell bekommt später ein eigenes Gegenstück mit denselben Schlüsseln; die
|
||||||
|
/// Registrierungen in Core und Modulen bleiben dabei unverändert.
|
||||||
|
/// </summary>
|
||||||
|
internal static class ViewIcons
|
||||||
|
{
|
||||||
|
/// <summary>Symbol-Schlüssel → Bildressource. Unbekannte Schlüssel liefern <c>null</c> (Text-only-Menüeintrag).</summary>
|
||||||
|
private static readonly Dictionary<string, Image> Map = new()
|
||||||
|
{
|
||||||
|
["dashboard"] = Properties.Resources.dashboard,
|
||||||
|
["settings"] = Properties.Resources.setting_tools,
|
||||||
|
["terminal"] = Properties.Resources.error_log,
|
||||||
|
["jobs"] = Properties.Resources.system_time,
|
||||||
|
["accounting"] = Properties.Resources.coins_in_hand,
|
||||||
|
["copytrading"] = Properties.Resources.cross_reference,
|
||||||
|
["resolutionfarming"] = Properties.Resources.file_start_workflow,
|
||||||
|
["supervisor"] = Properties.Resources.emotion_batman,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Standard-Symbolschlüssel je View-ID. Greift für Views, die selbst keinen
|
||||||
|
/// <see cref="ModuleView.IconKey"/> setzen – u.a. die Modul-Fenster, die die App-Ressourcen
|
||||||
|
/// nicht kennen. Ersetzt die frühere Icon-Zuweisung in <c>Program.AssignMenuIcons</c>.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Dictionary<string, string> DefaultKeyByViewId = new()
|
||||||
|
{
|
||||||
|
["core.dashboard"] = "dashboard",
|
||||||
|
["core.settings"] = "settings",
|
||||||
|
["core.terminal"] = "terminal",
|
||||||
|
["core.jobs"] = "jobs",
|
||||||
|
["accounting.main"] = "accounting",
|
||||||
|
["copytrading.main"] = "copytrading",
|
||||||
|
["resolutionfarming.main"] = "resolutionfarming",
|
||||||
|
["supervisor.main"] = "supervisor",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>Bildressource zum Schlüssel, oder <c>null</c> (kein Symbol / unbekannter Schlüssel).</summary>
|
||||||
|
public static Image? Resolve(string? iconKey) =>
|
||||||
|
iconKey != null && Map.TryGetValue(iconKey, out var img) ? img : null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setzt bei allen registrierten Views den Standard-Symbolschlüssel, sofern noch keiner gesetzt ist.
|
||||||
|
/// </summary>
|
||||||
|
public static void AssignDefaults(IModuleUiHost uiHost)
|
||||||
|
{
|
||||||
|
foreach (var view in uiHost.Views)
|
||||||
|
if (view.IconKey == null && DefaultKeyByViewId.TryGetValue(view.Id, out var key))
|
||||||
|
view.IconKey = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
using PolyTrader.Core.Modularity;
|
||||||
|
|
||||||
namespace PolyTrader.Core.Modularity
|
namespace PolyTraderSharp.Ui
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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
|
/// 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
|
/// 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).
|
/// <see cref="IModuleUiHost"/> nutzt, funktioniert es auch aus Modul-Fenstern (die die App nicht kennen).
|
||||||
@@ -57,11 +62,12 @@ namespace PolyTrader.Core.Modularity
|
|||||||
foreach (var view in host.Views.OrderBy(v => v.Order).ThenBy(v => v.Title))
|
foreach (var view in host.Views.OrderBy(v => v.Order).ThenBy(v => v.Title))
|
||||||
{
|
{
|
||||||
bool isCurrent = view.Id == currentViewId;
|
bool isCurrent = view.Id == currentViewId;
|
||||||
|
var image = ViewIcons.Resolve(view.IconKey);
|
||||||
var item = new ToolStripMenuItem(view.Title)
|
var item = new ToolStripMenuItem(view.Title)
|
||||||
{
|
{
|
||||||
Image = view.Icon,
|
Image = image,
|
||||||
ImageScaling = ToolStripItemImageScaling.SizeToFit,
|
ImageScaling = ToolStripItemImageScaling.SizeToFit,
|
||||||
DisplayStyle = view.Icon != null
|
DisplayStyle = image != null
|
||||||
? ToolStripItemDisplayStyle.ImageAndText
|
? ToolStripItemDisplayStyle.ImageAndText
|
||||||
: ToolStripItemDisplayStyle.Text,
|
: ToolStripItemDisplayStyle.Text,
|
||||||
Checked = isCurrent || host.IsOpen(view.Id)
|
Checked = isCurrent || host.IsOpen(view.Id)
|
||||||
@@ -1,21 +1,24 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Windows.Forms;
|
|
||||||
|
|
||||||
namespace PolyTrader.Core.Modularity
|
namespace PolyTrader.Core.Modularity
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Eine vom Core oder einem Modul beigesteuerte Fenster-Ansicht. Die eigentliche UI ist
|
/// Eine vom Core oder einem Modul beigesteuerte Fenster-Ansicht. Die Shell zeigt je View
|
||||||
/// ein designbares <see cref="Form"/>, das über <see cref="CreateForm"/> erzeugt wird
|
/// höchstens eine Instanz und holt ein bereits offenes Fenster wieder in den Vordergrund.
|
||||||
/// (mit DI-Abhängigkeiten). 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>
|
/// </summary>
|
||||||
public sealed class ModuleView
|
public sealed class ModuleView
|
||||||
{
|
{
|
||||||
/// <summary>Stabile ID für Einzelinstanz-Handling (nur ein Fenster je View).</summary>
|
/// <summary>Stabile ID für Einzelinstanz-Handling (nur ein Fenster je View).</summary>
|
||||||
public string Id { get; init; } = Guid.NewGuid().ToString();
|
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";
|
public string Title { get; init; } = "Fenster";
|
||||||
|
|
||||||
/// <summary>Optionale Gruppierung (z.B. "Core", "CopyTrading").</summary>
|
/// <summary>Optionale Gruppierung (z.B. "Core", "CopyTrading").</summary>
|
||||||
@@ -25,21 +28,26 @@ namespace PolyTrader.Core.Modularity
|
|||||||
public int Order { get; init; } = 0;
|
public int Order { get; init; } = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Optionales Icon für Menü/Buttons. Wird von der Shell/den Menüs genutzt. Settable, damit die
|
/// Logischer Schlüssel des Symbols für Menüs/Buttons (z.B. "dashboard", "money"). Die Shell
|
||||||
/// App den von Modulen registrierten Views zentral ein Icon aus ihren Ressourcen zuweisen kann
|
/// 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).
|
/// (Module referenzieren die App-Ressourcen nicht).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public System.Drawing.Image? Icon { get; set; }
|
public string? IconKey { get; set; }
|
||||||
|
|
||||||
/// <summary>Erzeugt das anzuzeigende Fenster (frische Instanz je Öffnung).</summary>
|
/// <summary>
|
||||||
public Func<Form> CreateForm { get; init; } = () => new Form();
|
/// 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>
|
/// <summary>
|
||||||
/// Wird der Shell beim Start übergeben; Core und Module registrieren hier ihre Ansichten.
|
/// 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.
|
/// 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
|
/// Ü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>
|
/// </summary>
|
||||||
public interface IModuleUiHost
|
public interface IModuleUiHost
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<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>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<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>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
@@ -57,7 +57,7 @@ namespace PolyTrader.Modules.Accounting
|
|||||||
Title = "Accounting",
|
Title = "Accounting",
|
||||||
Group = "Accounting",
|
Group = "Accounting",
|
||||||
Order = 400,
|
Order = 400,
|
||||||
CreateForm = () =>
|
CreateView = () =>
|
||||||
{
|
{
|
||||||
var form = new Ui.AccountingMainForm();
|
var form = new Ui.AccountingMainForm();
|
||||||
form.Initialize(services);
|
form.Initialize(services);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
@@ -92,7 +92,7 @@ namespace PolyTrader.Modules.CopyTrading
|
|||||||
Title = "Copytrading",
|
Title = "Copytrading",
|
||||||
Group = "CopyTrading",
|
Group = "CopyTrading",
|
||||||
Order = 100,
|
Order = 100,
|
||||||
CreateForm = () =>
|
CreateView = () =>
|
||||||
{
|
{
|
||||||
var form = new CopyTradingMainForm();
|
var form = new CopyTradingMainForm();
|
||||||
form.Initialize(services);
|
form.Initialize(services);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
@@ -63,7 +63,7 @@ namespace PolyTrader.Modules.ResolutionFarming
|
|||||||
Title = "ResolutionFarming",
|
Title = "ResolutionFarming",
|
||||||
Group = "ResolutionFarming",
|
Group = "ResolutionFarming",
|
||||||
Order = 200,
|
Order = 200,
|
||||||
CreateForm = () =>
|
CreateView = () =>
|
||||||
{
|
{
|
||||||
var form = new Ui.ResolutionFarmingMainForm();
|
var form = new Ui.ResolutionFarmingMainForm();
|
||||||
form.Initialize(services);
|
form.Initialize(services);
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ namespace PolyTrader.Modules.Supervisor
|
|||||||
Title = "Supervisor",
|
Title = "Supervisor",
|
||||||
Group = "Supervisor",
|
Group = "Supervisor",
|
||||||
Order = 300,
|
Order = 300,
|
||||||
CreateForm = () =>
|
CreateView = () =>
|
||||||
{
|
{
|
||||||
var form = new Ui.SupervisorMainForm();
|
var form = new Ui.SupervisorMainForm();
|
||||||
form.Initialize(services);
|
form.Initialize(services);
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using PolyTrader.Core.Modularity;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace PolyTrader.Tests
|
|
||||||
{
|
|
||||||
/// <summary>Sicherheitsnetz für das gemeinsame „Fenster"-Menü (Slice 1): Inhalt + Verhalten.</summary>
|
|
||||||
public class WindowMenuTests
|
|
||||||
{
|
|
||||||
private sealed class FakeUiHost : IModuleUiHost
|
|
||||||
{
|
|
||||||
public List<ModuleView> ViewList { get; } = new();
|
|
||||||
public HashSet<string> OpenIds { get; } = new();
|
|
||||||
public List<string> Opened { get; } = new();
|
|
||||||
public bool MainActivated { get; private set; }
|
|
||||||
|
|
||||||
public bool ShutdownRequested { get; private set; }
|
|
||||||
|
|
||||||
public IReadOnlyList<ModuleView> Views => ViewList;
|
|
||||||
public bool IsOpen(string viewId) => OpenIds.Contains(viewId);
|
|
||||||
public void OpenView(string viewId) => Opened.Add(viewId);
|
|
||||||
public void ActivateMain() => MainActivated = true;
|
|
||||||
public void RequestShutdown() => ShutdownRequested = true;
|
|
||||||
public void RegisterView(ModuleView view) => ViewList.Add(view);
|
|
||||||
public event Action? OpenStateChanged { add { } remove { } }
|
|
||||||
}
|
|
||||||
|
|
||||||
private static FakeUiHost Host()
|
|
||||||
{
|
|
||||||
var h = new FakeUiHost();
|
|
||||||
h.RegisterView(new ModuleView { Id = "core.dashboard", Title = "Dashboard", Order = 1 });
|
|
||||||
h.RegisterView(new ModuleView { Id = "core.terminal", Title = "Terminal / Logs", Order = 2 });
|
|
||||||
h.OpenIds.Add("core.terminal"); // Terminal ist offen
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Populate_lists_launcher_views_and_exit_side_by_side()
|
|
||||||
{
|
|
||||||
var host = Host();
|
|
||||||
var menu = new MenuStrip();
|
|
||||||
|
|
||||||
// currentViewId == null => Launcher (Hauptprozess): rechte Aktion ist „Beenden".
|
|
||||||
WindowMenu.Populate(menu, host, currentViewId: null);
|
|
||||||
|
|
||||||
var texts = menu.Items.OfType<ToolStripMenuItem>().Select(i => i.Text).ToList();
|
|
||||||
Assert.Equal("Launcher", texts[0]); // Launcher als erster Top-Level-Eintrag
|
|
||||||
Assert.Contains("Dashboard", texts);
|
|
||||||
Assert.Contains("Terminal / Logs", texts);
|
|
||||||
Assert.Contains("Beenden", texts);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Non_launcher_window_offers_close_window_not_exit()
|
|
||||||
{
|
|
||||||
var host = Host();
|
|
||||||
var menu = new MenuStrip();
|
|
||||||
|
|
||||||
// currentViewId != null => Modul-/Core-Fenster: KEIN „Beenden", nur „Fenster schließen".
|
|
||||||
WindowMenu.Populate(menu, host, currentViewId: "core.dashboard");
|
|
||||||
|
|
||||||
var texts = menu.Items.OfType<ToolStripMenuItem>().Select(i => i.Text).ToList();
|
|
||||||
Assert.Contains("Fenster schließen", texts);
|
|
||||||
Assert.DoesNotContain("Beenden", texts);
|
|
||||||
|
|
||||||
// Klick auf „Fenster schließen" fordert NICHT das App-Shutdown an.
|
|
||||||
menu.Items.OfType<ToolStripMenuItem>().First(i => i.Text == "Fenster schließen").PerformClick();
|
|
||||||
Assert.False(host.ShutdownRequested);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Views_carry_their_icon_in_the_menu()
|
|
||||||
{
|
|
||||||
var host = new FakeUiHost();
|
|
||||||
using var icon = new System.Drawing.Bitmap(16, 16);
|
|
||||||
host.RegisterView(new ModuleView { Id = "core.dashboard", Title = "Dashboard", Order = 1, Icon = icon });
|
|
||||||
var menu = new MenuStrip();
|
|
||||||
|
|
||||||
WindowMenu.Populate(menu, host, currentViewId: null);
|
|
||||||
|
|
||||||
var dashboard = menu.Items.OfType<ToolStripMenuItem>().First(i => i.Text == "Dashboard");
|
|
||||||
Assert.Same(icon, dashboard.Image);
|
|
||||||
Assert.Equal(ToolStripItemDisplayStyle.ImageAndText, dashboard.DisplayStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Current_view_is_bold_and_checked_open_view_is_checked()
|
|
||||||
{
|
|
||||||
var host = Host();
|
|
||||||
var menu = new MenuStrip();
|
|
||||||
|
|
||||||
WindowMenu.Populate(menu, host, currentViewId: "core.dashboard");
|
|
||||||
|
|
||||||
var dashboard = menu.Items.OfType<ToolStripMenuItem>().First(i => i.Text == "Dashboard");
|
|
||||||
var terminal = menu.Items.OfType<ToolStripMenuItem>().First(i => i.Text == "Terminal / Logs");
|
|
||||||
|
|
||||||
Assert.True(dashboard.Checked); // aktuelles Fenster
|
|
||||||
Assert.True(dashboard.Font.Bold);
|
|
||||||
Assert.True(terminal.Checked); // offenes Fenster
|
|
||||||
Assert.False(terminal.Font.Bold);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Clicking_items_navigates()
|
|
||||||
{
|
|
||||||
var host = Host();
|
|
||||||
var menu = new MenuStrip();
|
|
||||||
WindowMenu.Populate(menu, host, currentViewId: null);
|
|
||||||
|
|
||||||
var items = menu.Items.OfType<ToolStripMenuItem>().ToList();
|
|
||||||
items.First(i => i.Text == "Launcher").PerformClick();
|
|
||||||
items.First(i => i.Text == "Terminal / Logs").PerformClick();
|
|
||||||
|
|
||||||
Assert.True(host.MainActivated);
|
|
||||||
Assert.Contains("core.terminal", host.Opened);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Clicking_exit_requests_shutdown_not_immediate_exit()
|
|
||||||
{
|
|
||||||
var host = Host();
|
|
||||||
var menu = new MenuStrip();
|
|
||||||
WindowMenu.Populate(menu, host, currentViewId: null);
|
|
||||||
|
|
||||||
menu.Items.OfType<ToolStripMenuItem>().First(i => i.Text == "Beenden").PerformClick();
|
|
||||||
|
|
||||||
Assert.True(host.ShutdownRequested);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user