Files
IBKRTrader/src/IBKRTrader.App.Avalonia/Program.cs
T
RichardandClaude Opus 5 afae3c9c4a L3: Avalonia-Shell und Core-Ansichten - die Oberflaeche laeuft auf Linux
Neues Projekt IBKRTrader.App.Avalonia (net10.0, plattformneutral) mit
Launcher, Fenster-Menue, Beenden-Abfrage und den vier Core-Ansichten
Dashboard, Workers, Logs und Settings. Nutzt denselben AppHostBuilder wie
der Daemon; ShellServices ergaenzt nur, was ohne Oberflaeche nicht existiert.

Versionen bewusst auf der 11er-Linie: Avalonia 11.3.19, DataGrid 11.3.13.
LiveCharts2 2.0.5 ist gegen Avalonia 11 gebaut und bricht unter 12
(Gestures.PinchEvent gibt es dort nicht mehr). Diagramme kommen mit den
neuen Modulen - bis LiveCharts2 Avalonia 12 unterstuetzt, darf hier nicht
angehoben werden. Erfahrung aus PolytraderSharp, im csproj vermerkt.

Ersatz fuer WinForms-Bausteine ohne Gegenstueck:
- PropertyGrid -> SettingsModelBuilder erzeugt die Maske aus den bereits
  vorhandenen Category-/DisplayName-/Description-Attributen von AppSettings.
  Eine neue Einstellung erscheint damit automatisch, ohne dass jemand die
  Oberflaeche anfasst - genau der Vorteil des PropertyGrid. Kennwortfelder
  (DB-Passwort, Flex-Token) werden verdeckt. Gemessen: 11 Abschnitte,
  41 Felder.
- MessageBox.Show -> ShutdownConfirmWindow (Avalonia bringt keinen
  Meldungsdialog mit). Schliessen ueber das X zaehlt als Abbruch, damit ein
  versehentlicher Klick nie den Handelsbetrieb stoppt.
- RichTextBox mit SelectionColor -> eingefaerbte Elemente je Logzeile, mit
  Filter, Auto-Scroll und Zeilenbegrenzung (im Dauerbetrieb waere die Liste
  sonst unbegrenzt gewachsen).
- ToolStrip/StatusStrip -> zentrale Stilklassen in App.axaml (toolbar,
  statusbar, kpi, section). Die neuen Module setzen darauf auf.

Gemeinsame Bausteine bewusst jetzt schon zentral, weil die neuen Module
direkt in Avalonia entwickelt werden sollen.

Kompilierte Bindings sind aktiv (x:DataType je Datenkontext) - Tippfehler in
Bindings fallen damit beim Kompilieren auf statt erst zur Laufzeit. Dafuer
brauchte es benannte Record-Zeilentypen statt der anonymen Typen, die
DataGridView.DataSource frueher bekommen hat.

--smoke-ui laeuft jetzt ueber SetupWithoutStarting, also OHNE Anzeigegeraet
und ohne laufende Dienste. Das war mit WinForms nicht moeglich und macht die
Konstruktionspruefung erstmals CI-tauglich. Der Host wird dabei bewusst nicht
gestartet, sonst liefen Worker und Broker-Verbindungen gegen die echten
Endpunkte an.

AVLN3001 unterdrueckt: die Fenster haben absichtlich keinen parameterlosen
Konstruktor. Einer wuerde sie ohne ihre Dienste konstruierbar machen und
genau den Fehler verdecken, den die Konstruktionspruefung finden soll.

Verifiziert: Build 0 Fehler/0 Warnungen, 193 Tests gruen, Avalonia-Smoke-UI
konstruiert alle 6 Fenster, publish -r linux-x64 liefert 32 MB mit
ELF-Launcher samt libSkiaSharp.so/libHarfBuzzSharp.so und ohne
Windows-Abhaengigkeiten. Die WinForms-Shell ist unveraendert lauffaehig.

Offen fuer L4: die drei Modul-Fenster (ModuleViews ist noch leer).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 10:18:44 +02:00

82 lines
2.9 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 Avalonia;
using IBKRTrader.App.Avalonia.Shell;
using IBKRTrader.Core.Logging;
using IBKRTrader.Core.Modularity;
using IBKRTrader.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace IBKRTrader.App.Avalonia;
/// <summary>
/// Einstiegspunkt der plattformneutralen Oberfläche.
///
/// <para>Bewusst zweigeteilt: <see cref="AppHostBuilder"/> stellt den Host mit Persistenz,
/// Diensten und Modulen zusammen ohne jeden Bezug zur Oberfläche. Erst danach wird Avalonia
/// daran gehängt. Derselbe Host trägt den kopflosen Linux-Dienst
/// (<c>IBKRTrader.Daemon</c>).</para>
/// </summary>
internal static class Program
{
[STAThread]
public static int Main(string[] args)
{
// Konstruktionsprüfung aller Fenster ohne Message-Loop und ohne laufende Dienste.
if (HasFlag(args, "--smoke-ui")) return SmokeUi.Run();
var modules = AppHostBuilder.CreateModules();
using var host = AppHostBuilder.Build(modules, ShellServices.Register);
AppHostBuilder.RunStartupChecks(host.Services);
var logger = host.Services.GetRequiredService<LoggingService>();
logger.Info("Core", "=== IBKRTrader startet ===");
logger.Info("Core", $"Version: 1.0.0 | .NET {Environment.Version}");
host.Start();
StartModules(modules, logger);
try
{
App.Services = host.Services;
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
return 0;
}
finally
{
// Geordnetes Herunterfahren erst nachdem die Oberfläche beendet ist.
StopModules(modules, logger);
host.StopAsync(TimeSpan.FromSeconds(30)).GetAwaiter().GetResult();
}
}
/// <summary>Von Avalonia erwartete Fabrikmethode (auch vom XAML-Previewer genutzt).</summary>
public static AppBuilder BuildAvaloniaApp() =>
AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
private static void StartModules(IReadOnlyList<IModule> modules, LoggingService logger)
{
foreach (var module in modules)
{
try { module.StartAsync(default).GetAwaiter().GetResult(); }
catch (Exception ex) { logger.Error(module.Name, $"{module.Name}: Start fehlgeschlagen.", ex); }
}
logger.Info("Core", "IBKRTrader bereit.");
}
private static void StopModules(IReadOnlyList<IModule> modules, LoggingService logger)
{
foreach (var module in modules)
{
try { module.StopAsync(default).GetAwaiter().GetResult(); }
catch (Exception ex) { logger.Warn(module.Name, $"{module.Name}: Stopp fehlgeschlagen: {ex.Message}"); }
}
}
private static bool HasFlag(string[] args, string flag) =>
args.Any(a => string.Equals(a, flag, StringComparison.OrdinalIgnoreCase));
}