Files
PolyTraderSharp/src/PolyTrader.Modules.CopyTrading/Ui/MasterTradersView.cs
T
RichardandClaude Opus 4.8 76007db93f Modul-Views: parameterloser Ctor + Initialize() (Designer-öffenbar)
Der VS-WinForms-Designer braucht einen parameterlosen Konstruktor, um eine Form
zu instanziieren. Die drei Views hatten nur einen DI-Ctor -> Designer hätte sie
nicht öffnen können. Umgestellt auf das Core-View-Muster (DashboardView):
- Parameterloser Ctor: InitializeComponent() + Event-Wiring.
- Initialize(deps): Abhängigkeiten setzen + Daten laden.
- Felder nullable + Null-Guards in den Handlern.
- CopyTradingModule.RegisterUi: new View() + view.Initialize(...) statt DI-Ctor.

Verifiziert: Build grün, --smoke-ui grün (3 Accounts / 32 Trader, alle Views OK).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:15:04 +02:00

135 lines
5.0 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 System;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;
using PolyTrader.Modules.CopyTrading.Persistence;
using PolyTraderSharp;
using PolyTraderSharp.Models;
namespace PolyTrader.Modules.CopyTrading.Ui
{
/// <summary>
/// Verwaltung der Master-Trader (mod_copytrading_traders): Liste, Detail-Editor
/// (PropertyGrid) und Account-Zuweisung (welche Accounts diesen Trader kopieren).
/// Persistiert über das Repo und hält den Hot-Path-State
/// (<see cref="CopyTradingState.Traders"/>) synchron.
/// Layout im Designer (MasterTradersView.Designer.cs), Daten/Logik hier.
/// </summary>
public partial class MasterTradersView : Form
{
private ITrackedTraderRepository? _repo;
private TradingState? _state;
private CopyTradingState? _copyState;
private BindingList<TrackedTrader> _binding = new();
private TrackedTrader? _current;
// Parameterloser Konstruktor für den WinForms-Designer.
public MasterTradersView()
{
InitializeComponent();
colWinrate.DefaultCellStyle.Format = "F1";
colPnl.DefaultCellStyle.Format = "F2";
tsNew.Click += (_, _) => AddNew();
tsSave.Click += (_, _) => SaveCurrent();
tsDelete.Click += (_, _) => DeleteCurrent();
tsRefresh.Click += (_, _) => LoadData();
grid.SelectionChanged += (_, _) => OnSelectionChanged();
}
/// <summary>Injiziert die Abhängigkeiten (nach der DI-Auflösung) und lädt die Trader.</summary>
public void Initialize(ITrackedTraderRepository repo, TradingState state, CopyTradingState copyState)
{
_repo = repo;
_state = state;
_copyState = copyState;
LoadData();
}
private void LoadData()
{
if (_repo == null) return;
var traders = _repo.GetAll().OrderBy(t => t.Id).ToList();
_binding = new BindingList<TrackedTrader>(traders);
grid.DataSource = _binding;
if (traders.Count > 0)
grid.CurrentCell = grid.Rows[0].Cells[0];
else
ClearDetail();
lblHint.Text = $"{traders.Count} Master-Trader geladen.";
}
private void OnSelectionChanged()
{
if (grid.CurrentRow?.DataBoundItem is TrackedTrader t)
BindDetail(t);
}
private void BindDetail(TrackedTrader trader)
{
_current = trader;
pgDetail.SelectedObject = trader;
clbAccounts.Items.Clear();
if (_state == null) return;
foreach (var acc in _state.Accounts.Values.OrderBy(a => a.AccountId))
{
string label = string.IsNullOrEmpty(acc.Name) ? $"#{acc.AccountId}" : $"{acc.Name} (#{acc.AccountId}){(acc.IsDemo ? " · Demo" : "")}";
int idx = clbAccounts.Items.Add(new AccountItem(acc.AccountId, label));
clbAccounts.SetItemChecked(idx, trader.AssignedAccountIds.Contains(acc.AccountId));
}
}
private void ClearDetail()
{
_current = null;
pgDetail.SelectedObject = null;
clbAccounts.Items.Clear();
}
private void AddNew()
{
int nextId = _binding.Count > 0 ? _binding.Max(t => t.Id) + 1 : 1;
var trader = new TrackedTrader { Id = nextId, DisplayName = $"Neuer Trader {nextId}" };
_binding.Add(trader);
grid.CurrentCell = grid.Rows[_binding.Count - 1].Cells[0];
lblHint.Text = $"Neuer Master-Trader #{nextId} Felder ausfüllen und Speichern.";
}
private void SaveCurrent()
{
if (_current == null || _repo == null || _copyState == null) { lblHint.Text = "Kein Trader ausgewählt."; return; }
_current.AssignedAccountIds = clbAccounts.CheckedItems.Cast<AccountItem>().Select(a => a.Id).ToHashSet();
_repo.Upsert(_current);
_copyState.Traders[_current.Id] = _current;
grid.Refresh();
lblHint.Text = $"Gespeichert: #{_current.Id} {_current.DisplayName} ({_current.AssignedAccountIds.Count} Account(s)) um {DateTime.Now:HH:mm:ss}.";
}
private void DeleteCurrent()
{
if (_current == null || _repo == null || _copyState == null) { lblHint.Text = "Kein Trader ausgewählt."; return; }
var id = _current.Id;
if (MessageBox.Show($"Master-Trader #{id} ({_current.DisplayName}) wirklich löschen?",
"Löschen bestätigen", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes)
return;
_repo.Delete(id);
_copyState.Traders.TryRemove(id, out _);
_binding.Remove(_current);
ClearDetail();
lblHint.Text = $"Master-Trader #{id} gelöscht.";
}
private sealed record AccountItem(int Id, string Label)
{
public override string ToString() => Label;
}
}
}