Die drei Modul-Views wurden von komplett code-first (alle Controls im Konstruktor) auf das Standard-WinForms-Designer-Muster umgestellt (je X.cs + X.Designer.cs mit InitializeComponent), damit sie im Designer geöffnet und bearbeitet werden können: - ClosedTradesView, AccountSettingsView, MasterTradersView: alle wichtigen Steuerelemente (Panels, DataGridView mit expliziten Spalten via AutoGenerateColumns=false, PropertyGrid, ToolStrip, GroupBox, CheckedListBox, ComboBox, Buttons, Labels, Splitter) sind jetzt Designer-Felder. - Konsistent mit den Core-Views (DashboardView-Muster): Layout im Designer, Zellformate/Event-Wiring/Datenbindung im Code. Verifiziert: --smoke-ui grün (3 Accounts / 32 Trader; alle Views + Launcher OK). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
128 lines
4.7 KiB
C#
128 lines
4.7 KiB
C#
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 readonly ITrackedTraderRepository _repo;
|
||
private readonly TradingState _state;
|
||
private readonly CopyTradingState _copyState;
|
||
|
||
private BindingList<TrackedTrader> _binding = new();
|
||
private TrackedTrader? _current;
|
||
|
||
public MasterTradersView(ITrackedTraderRepository repo, TradingState state, CopyTradingState copyState)
|
||
{
|
||
_repo = repo;
|
||
_state = state;
|
||
_copyState = copyState;
|
||
|
||
InitializeComponent();
|
||
|
||
colWinrate.DefaultCellStyle.Format = "F1";
|
||
colPnl.DefaultCellStyle.Format = "F2";
|
||
|
||
tsNew.Click += (_, _) => AddNew();
|
||
tsSave.Click += (_, _) => SaveCurrent();
|
||
tsDelete.Click += (_, _) => DeleteCurrent();
|
||
tsRefresh.Click += (_, _) => LoadData();
|
||
grid.SelectionChanged += (_, _) => OnSelectionChanged();
|
||
|
||
LoadData();
|
||
}
|
||
|
||
private void LoadData()
|
||
{
|
||
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();
|
||
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) { 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) { 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;
|
||
}
|
||
}
|
||
}
|