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>
This commit is contained in:
Richard
2026-07-06 10:15:04 +02:00
co-authored by Claude Opus 4.8
parent 4caba5bfd1
commit 76007db93f
4 changed files with 76 additions and 42 deletions
@@ -62,10 +62,15 @@ namespace PolyTrader.Modules.CopyTrading
Title = "Master-Trader", Title = "Master-Trader",
Group = "CopyTrading", Group = "CopyTrading",
Order = 100, Order = 100,
CreateForm = () => new MasterTradersView( CreateForm = () =>
services.GetRequiredService<ITrackedTraderRepository>(), {
services.GetRequiredService<TradingState>(), var view = new MasterTradersView();
services.GetRequiredService<CopyTradingState>()) view.Initialize(
services.GetRequiredService<ITrackedTraderRepository>(),
services.GetRequiredService<TradingState>(),
services.GetRequiredService<CopyTradingState>());
return view;
}
}); });
host.RegisterView(new ModuleView host.RegisterView(new ModuleView
@@ -74,10 +79,15 @@ namespace PolyTrader.Modules.CopyTrading
Title = "Geschlossene Copytrades", Title = "Geschlossene Copytrades",
Group = "CopyTrading", Group = "CopyTrading",
Order = 110, Order = 110,
CreateForm = () => new ClosedTradesView( CreateForm = () =>
services.GetRequiredService<ICopyTradeLogRepository>(), {
services.GetRequiredService<TradingState>(), var view = new ClosedTradesView();
services.GetRequiredService<CopyTradingState>()) view.Initialize(
services.GetRequiredService<ICopyTradeLogRepository>(),
services.GetRequiredService<TradingState>(),
services.GetRequiredService<CopyTradingState>());
return view;
}
}); });
host.RegisterView(new ModuleView host.RegisterView(new ModuleView
@@ -86,10 +96,15 @@ namespace PolyTrader.Modules.CopyTrading
Title = "Copytrading-Account-Einstellungen", Title = "Copytrading-Account-Einstellungen",
Group = "CopyTrading", Group = "CopyTrading",
Order = 120, Order = 120,
CreateForm = () => new AccountSettingsView( CreateForm = () =>
services.GetRequiredService<ICopyTradingAccountSettingsRepository>(), {
services.GetRequiredService<TradingState>(), var view = new AccountSettingsView();
services.GetRequiredService<CopyTradingState>()) view.Initialize(
services.GetRequiredService<ICopyTradingAccountSettingsRepository>(),
services.GetRequiredService<TradingState>(),
services.GetRequiredService<CopyTradingState>());
return view;
}
}); });
} }
@@ -15,28 +15,34 @@ namespace PolyTrader.Modules.CopyTrading.Ui
/// </summary> /// </summary>
public partial class AccountSettingsView : Form public partial class AccountSettingsView : Form
{ {
private readonly ICopyTradingAccountSettingsRepository _repo; private ICopyTradingAccountSettingsRepository? _repo;
private readonly TradingState _state; private TradingState? _state;
private readonly CopyTradingState _copyState; private CopyTradingState? _copyState;
private CopyTradingAccountSettings? _current; private CopyTradingAccountSettings? _current;
public AccountSettingsView(ICopyTradingAccountSettingsRepository repo, TradingState state, CopyTradingState copyState) // Parameterloser Konstruktor für den WinForms-Designer.
public AccountSettingsView()
{ {
_repo = repo;
_state = state;
_copyState = copyState;
InitializeComponent(); InitializeComponent();
cmbAccounts.SelectedIndexChanged += (_, _) => LoadSelected(); cmbAccounts.SelectedIndexChanged += (_, _) => LoadSelected();
btnSave.Click += (_, _) => Save(); btnSave.Click += (_, _) => Save();
}
/// <summary>Injiziert die Abhängigkeiten (nach der DI-Auflösung) und füllt die Auswahl.</summary>
public void Initialize(ICopyTradingAccountSettingsRepository repo, TradingState state, CopyTradingState copyState)
{
_repo = repo;
_state = state;
_copyState = copyState;
PopulateAccounts(); PopulateAccounts();
} }
private void PopulateAccounts() private void PopulateAccounts()
{ {
if (_state == null) return;
var items = _state.Accounts.Values var items = _state.Accounts.Values
.OrderBy(a => a.AccountId) .OrderBy(a => a.AccountId)
.Select(a => new AccountItem(a.AccountId, string.IsNullOrEmpty(a.Name) ? $"#{a.AccountId}" : $"{a.Name} (#{a.AccountId}){(a.IsDemo ? " · Demo" : "")}")) .Select(a => new AccountItem(a.AccountId, string.IsNullOrEmpty(a.Name) ? $"#{a.AccountId}" : $"{a.Name} (#{a.AccountId}){(a.IsDemo ? " · Demo" : "")}"))
@@ -52,7 +58,7 @@ namespace PolyTrader.Modules.CopyTrading.Ui
private void LoadSelected() private void LoadSelected()
{ {
if (cmbAccounts.SelectedItem is not AccountItem item) return; if (_repo == null || cmbAccounts.SelectedItem is not AccountItem item) return;
_current = _repo.Get(item.Id) ?? new CopyTradingAccountSettings { AccountId = item.Id }; _current = _repo.Get(item.Id) ?? new CopyTradingAccountSettings { AccountId = item.Id };
pgSettings.SelectedObject = _current; pgSettings.SelectedObject = _current;
lblHint.Text = $"Einstellungen für Account #{item.Id}."; lblHint.Text = $"Einstellungen für Account #{item.Id}.";
@@ -60,7 +66,7 @@ namespace PolyTrader.Modules.CopyTrading.Ui
private void Save() private void Save()
{ {
if (_current == null) return; if (_current == null || _repo == null || _copyState == null) return;
_repo.Upsert(_current); _repo.Upsert(_current);
_copyState.AccountSettings[_current.AccountId] = _current; _copyState.AccountSettings[_current.AccountId] = _current;
lblHint.Text = $"Gespeichert für Account #{_current.AccountId} um {DateTime.Now:HH:mm:ss}."; lblHint.Text = $"Gespeichert für Account #{_current.AccountId} um {DateTime.Now:HH:mm:ss}.";
@@ -15,16 +15,13 @@ namespace PolyTrader.Modules.CopyTrading.Ui
/// </summary> /// </summary>
public partial class ClosedTradesView : Form public partial class ClosedTradesView : Form
{ {
private readonly ICopyTradeLogRepository _tradeLog; private ICopyTradeLogRepository? _tradeLog;
private readonly TradingState _state; private TradingState? _state;
private readonly CopyTradingState _copyState; private CopyTradingState? _copyState;
public ClosedTradesView(ICopyTradeLogRepository tradeLog, TradingState state, CopyTradingState copyState) // Parameterloser Konstruktor für den WinForms-Designer.
public ClosedTradesView()
{ {
_tradeLog = tradeLog;
_state = state;
_copyState = copyState;
InitializeComponent(); InitializeComponent();
colEntry.DefaultCellStyle.Format = "F3"; colEntry.DefaultCellStyle.Format = "F3";
@@ -36,12 +33,21 @@ namespace PolyTrader.Modules.CopyTrading.Ui
colClosedAt.DefaultCellStyle.Format = "dd.MM.yyyy HH:mm"; colClosedAt.DefaultCellStyle.Format = "dd.MM.yyyy HH:mm";
btnRefresh.Click += (_, _) => LoadData(); btnRefresh.Click += (_, _) => LoadData();
}
/// <summary>Injiziert die Abhängigkeiten (nach der DI-Auflösung) und lädt die Daten.</summary>
public void Initialize(ICopyTradeLogRepository tradeLog, TradingState state, CopyTradingState copyState)
{
_tradeLog = tradeLog;
_state = state;
_copyState = copyState;
LoadData(); LoadData();
} }
private void LoadData() private void LoadData()
{ {
if (_tradeLog == null) return;
var rows = _tradeLog.Find(_ => true) var rows = _tradeLog.Find(_ => true)
.OrderByDescending(t => t.ClosedAt) .OrderByDescending(t => t.ClosedAt)
.Select(t => new ClosedTradeRow .Select(t => new ClosedTradeRow
@@ -80,14 +86,14 @@ namespace PolyTrader.Modules.CopyTrading.Ui
private string ResolveAccount(int accountId, bool isDemo) private string ResolveAccount(int accountId, bool isDemo)
{ {
string suffix = isDemo ? " (Demo)" : ""; string suffix = isDemo ? " (Demo)" : "";
if (_state.Accounts.TryGetValue(accountId, out var acc) && !string.IsNullOrEmpty(acc.Name)) if (_state != null && _state.Accounts.TryGetValue(accountId, out var acc) && !string.IsNullOrEmpty(acc.Name))
return acc.Name + suffix; return acc.Name + suffix;
return $"#{accountId}{suffix}"; return $"#{accountId}{suffix}";
} }
private string ResolveTrader(int traderId) private string ResolveTrader(int traderId)
{ {
if (_copyState.Traders.TryGetValue(traderId, out var t) && !string.IsNullOrEmpty(t.DisplayName)) if (_copyState != null && _copyState.Traders.TryGetValue(traderId, out var t) && !string.IsNullOrEmpty(t.DisplayName))
return t.DisplayName; return t.DisplayName;
return traderId > 0 ? $"#{traderId}" : "Unbekannt"; return traderId > 0 ? $"#{traderId}" : "Unbekannt";
} }
@@ -17,19 +17,16 @@ namespace PolyTrader.Modules.CopyTrading.Ui
/// </summary> /// </summary>
public partial class MasterTradersView : Form public partial class MasterTradersView : Form
{ {
private readonly ITrackedTraderRepository _repo; private ITrackedTraderRepository? _repo;
private readonly TradingState _state; private TradingState? _state;
private readonly CopyTradingState _copyState; private CopyTradingState? _copyState;
private BindingList<TrackedTrader> _binding = new(); private BindingList<TrackedTrader> _binding = new();
private TrackedTrader? _current; private TrackedTrader? _current;
public MasterTradersView(ITrackedTraderRepository repo, TradingState state, CopyTradingState copyState) // Parameterloser Konstruktor für den WinForms-Designer.
public MasterTradersView()
{ {
_repo = repo;
_state = state;
_copyState = copyState;
InitializeComponent(); InitializeComponent();
colWinrate.DefaultCellStyle.Format = "F1"; colWinrate.DefaultCellStyle.Format = "F1";
@@ -40,12 +37,21 @@ namespace PolyTrader.Modules.CopyTrading.Ui
tsDelete.Click += (_, _) => DeleteCurrent(); tsDelete.Click += (_, _) => DeleteCurrent();
tsRefresh.Click += (_, _) => LoadData(); tsRefresh.Click += (_, _) => LoadData();
grid.SelectionChanged += (_, _) => OnSelectionChanged(); 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(); LoadData();
} }
private void LoadData() private void LoadData()
{ {
if (_repo == null) return;
var traders = _repo.GetAll().OrderBy(t => t.Id).ToList(); var traders = _repo.GetAll().OrderBy(t => t.Id).ToList();
_binding = new BindingList<TrackedTrader>(traders); _binding = new BindingList<TrackedTrader>(traders);
grid.DataSource = _binding; grid.DataSource = _binding;
@@ -68,6 +74,7 @@ namespace PolyTrader.Modules.CopyTrading.Ui
pgDetail.SelectedObject = trader; pgDetail.SelectedObject = trader;
clbAccounts.Items.Clear(); clbAccounts.Items.Clear();
if (_state == null) return;
foreach (var acc in _state.Accounts.Values.OrderBy(a => a.AccountId)) 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" : "")}"; string label = string.IsNullOrEmpty(acc.Name) ? $"#{acc.AccountId}" : $"{acc.Name} (#{acc.AccountId}){(acc.IsDemo ? " · Demo" : "")}";
@@ -94,7 +101,7 @@ namespace PolyTrader.Modules.CopyTrading.Ui
private void SaveCurrent() private void SaveCurrent()
{ {
if (_current == null) { lblHint.Text = "Kein Trader ausgewählt."; return; } 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(); _current.AssignedAccountIds = clbAccounts.CheckedItems.Cast<AccountItem>().Select(a => a.Id).ToHashSet();
@@ -106,7 +113,7 @@ namespace PolyTrader.Modules.CopyTrading.Ui
private void DeleteCurrent() private void DeleteCurrent()
{ {
if (_current == null) { lblHint.Text = "Kein Trader ausgewählt."; return; } if (_current == null || _repo == null || _copyState == null) { lblHint.Text = "Kein Trader ausgewählt."; return; }
var id = _current.Id; var id = _current.Id;
if (MessageBox.Show($"Master-Trader #{id} ({_current.DisplayName}) wirklich löschen?", if (MessageBox.Show($"Master-Trader #{id} ({_current.DisplayName}) wirklich löschen?",
"Löschen bestätigen", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes) "Löschen bestätigen", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes)