using System.ComponentModel;
using System.Reflection;
namespace IBKRTrader.App.Avalonia.ViewModels;
/// Ein bearbeitbares Einzelfeld der Einstellungen.
public sealed class SettingsField
{
public required string DisplayName { get; init; }
public required string Description { get; init; }
public required Type ValueType { get; init; }
public required bool IsPassword { get; init; }
public required Func Get { get; init; }
public required Action Set { get; init; }
}
/// Ein Abschnitt (entspricht einem aufklappbaren Knoten des früheren PropertyGrid).
public sealed record SettingsSection(string Title, IReadOnlyList Fields);
///
/// Baut die Eingabemaske der Einstellungen aus den Attributen von AppSettings .
///
/// Warum aus Attributen: Die WinForms-Fassung zeigte AppSettings in einem
/// PropertyGrid . Avalonia hat dafür kein Gegenstück. Die Klassen tragen bereits
/// , und
/// – daraus lässt sich die Maske erzeugen, statt sie von Hand
/// zu pflegen. Eine neue Einstellung erscheint damit automatisch, ohne dass jemand die Oberfläche
/// anfasst; genau das war der Vorteil des PropertyGrid, und er bleibt erhalten.
///
public static class SettingsModelBuilder
{
/// Typen, die als Eingabefeld dargestellt werden. Alles andere gilt als Unterabschnitt.
private static bool IsLeaf(Type t) =>
t == typeof(string) || t.IsEnum ||
t == typeof(int) || t == typeof(long) || t == typeof(double) ||
t == typeof(decimal) || t == typeof(bool);
/// Zerlegt das Einstellungsobjekt in Abschnitte mit Feldern.
public static IReadOnlyList Build(object root)
{
var sections = new List();
Walk(root, prefix: null, sections);
return sections;
}
private static void Walk(object owner, string? prefix, List sections)
{
var fields = new List();
foreach (var prop in owner.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!prop.CanRead || prop.GetIndexParameters().Length > 0) continue;
var title = prop.GetCustomAttribute()?.DisplayName ?? prop.Name;
if (IsLeaf(prop.PropertyType))
{
if (!prop.CanWrite) continue; // z. B. berechnete Eigenschaften
var target = owner; // für den Abschluss festhalten
fields.Add(new SettingsField
{
DisplayName = title,
Description = prop.GetCustomAttribute()?.Description ?? "",
ValueType = prop.PropertyType,
IsPassword = prop.GetCustomAttribute()?.Password == true,
Get = () => prop.GetValue(target),
Set = v => prop.SetValue(target, v)
});
continue;
}
// Verschachteltes Einstellungsobjekt → eigener Abschnitt. Nur eigene Typen verfolgen,
// damit die Rekursion nicht in Framework-Typen abbiegt.
if (prop.PropertyType.IsClass && prop.PropertyType.Namespace?.StartsWith("IBKRTrader") == true)
{
var child = prop.GetValue(owner);
if (child is not null)
Walk(child, prefix is null ? title : $"{prefix} · {title}", sections);
}
}
if (fields.Count > 0)
sections.Add(new SettingsSection(prefix ?? "Allgemein", fields));
}
}