From b12809bf34c9111e8f6511115dd9603176e287cc Mon Sep 17 00:00:00 2001 From: toolsche Date: Fri, 12 Jun 2026 17:16:10 +0200 Subject: [PATCH 1/2] WPF sample: fix blank chart on non-English locales and related bugs - Chart.cs: format OHLC values with InvariantCulture. They were interpolated with the current culture, so on cultures using ',' as the decimal separator (e.g. German) createChart received invalid JavaScript and the chart stayed blank. - WPF.Sample.csproj: add CopyToOutputDirectory for hammer.js and chartjs-plugin-zoom.min.js. They are referenced by chart.html but only had CopyToPublishDirectory, so they were missing from a normal build output. - ChartingService.cs: load chart.html via a proper file:// URI resolved from the app directory (AppContext.BaseDirectory) instead of Environment.CurrentDirectory plus a raw Windows path. - ShellViewModel.cs: de-duplicate conversion-symbol requests on account change. Each distinct quote asset is now requested once instead of once per symbol, cutting hundreds of sequential round-trips and bringing account load from minutes to seconds. Co-Authored-By: Claude Opus 4.8 --- samples/WPF.Sample/Services/Chart.cs | 13 +++++++--- .../WPF.Sample/Services/ChartingService.cs | 6 ++++- .../WPF.Sample/ViewModels/ShellViewModel.cs | 26 ++++++++++++++----- samples/WPF.Sample/WPF.Sample.csproj | 2 ++ 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/samples/WPF.Sample/Services/Chart.cs b/samples/WPF.Sample/Services/Chart.cs index 5d20f74..dd0645b 100644 --- a/samples/WPF.Sample/Services/Chart.cs +++ b/samples/WPF.Sample/Services/Chart.cs @@ -1,6 +1,7 @@ -using CefSharp; +using CefSharp; using CefSharp.Wpf; using System.Collections.Generic; +using System.Globalization; using System.Linq; namespace Trading.UI.Sample.Services @@ -14,9 +15,15 @@ public class Chart : ChromiumWebBrowser, IChart { public void LoadData(string name, IEnumerable data) { - var dataStr = data.Select(ohlc => $"{{t: {ohlc.Time.ToUnixTimeMilliseconds()},o: {ohlc.Open},h: {ohlc.High},l: {ohlc.Low},c: {ohlc.Close}}}"); + // Format the numbers with InvariantCulture so they always use '.' as the decimal + // separator. With the current culture, cultures that use ',' (e.g. German) produced + // invalid JavaScript like "o: 1,2345", which threw a SyntaxError in createChart and + // left the chart blank. + var dataStr = data.Select(ohlc => string.Format(CultureInfo.InvariantCulture, + "{{t: {0},o: {1},h: {2},l: {3},c: {4}}}", + ohlc.Time.ToUnixTimeMilliseconds(), ohlc.Open, ohlc.High, ohlc.Low, ohlc.Close)); this.GetMainFrame().ExecuteJavaScriptAsync($"createChart('{name}',[{string.Join(',', dataStr)}]);"); } } -} \ No newline at end of file +} diff --git a/samples/WPF.Sample/Services/ChartingService.cs b/samples/WPF.Sample/Services/ChartingService.cs index 64b7eee..489b21e 100644 --- a/samples/WPF.Sample/Services/ChartingService.cs +++ b/samples/WPF.Sample/Services/ChartingService.cs @@ -35,7 +35,11 @@ public IChart GetChart() private void Browser_Loaded(object sender, System.Windows.RoutedEventArgs e) { - (sender as ChromiumWebBrowser).Address = Path.Combine(Environment.CurrentDirectory, "Chart.js", "chart.html"); + // Resolve relative to the app directory (not the current working directory, which can + // differ depending on how the app was launched) and hand CefSharp a proper file:// URI. + var chartHtmlPath = Path.Combine(AppContext.BaseDirectory, "Chart.js", "chart.html"); + + (sender as ChromiumWebBrowser).Address = new Uri(chartHtmlPath).AbsoluteUri; } } } \ No newline at end of file diff --git a/samples/WPF.Sample/ViewModels/ShellViewModel.cs b/samples/WPF.Sample/ViewModels/ShellViewModel.cs index e60a412..8defb2f 100644 --- a/samples/WPF.Sample/ViewModels/ShellViewModel.cs +++ b/samples/WPF.Sample/ViewModels/ShellViewModel.cs @@ -7,6 +7,7 @@ using Prism.Regions; using Prism.Services.Dialogs; using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reactive.Linq; @@ -407,20 +408,31 @@ private async void OnAccountChanged() private async Task FillConversionSymbols(AccountModel account) { + // The conversion chain only depends on the symbol's quote asset (the deposit asset is + // constant for the account), so request each distinct quote asset once and reuse the + // result for every symbol that shares it. This turns hundreds of sequential network + // round-trips into a handful, which is the main driver of the account-load time. + var conversionSymbolsCache = new Dictionary>(); + foreach (var symbol in account.Symbols) { - if (symbol.QuoteAsset.AssetId != account.DepositAsset.AssetId) + if (symbol.QuoteAsset.AssetId == account.DepositAsset.AssetId) { - var conversionLightSymbols = await _apiService.GetConversionSymbols(account.Id, account.IsLive, symbol.QuoteAsset.AssetId, account.DepositAsset.AssetId); - - var conversionSymbolModels = conversionLightSymbols.Select(iLightSymbol => account.Symbols.FirstOrDefault(iSymbol => iSymbol.Id == iLightSymbol.SymbolId)).Where(iSymbol => iSymbol is not null); + symbol.ConversionSymbols.Add(symbol); - symbol.ConversionSymbols.AddRange(conversionSymbolModels); + continue; } - else + + if (conversionSymbolsCache.TryGetValue(symbol.QuoteAsset.AssetId, out var conversionSymbolModels) is false) { - symbol.ConversionSymbols.Add(symbol); + var conversionLightSymbols = await _apiService.GetConversionSymbols(account.Id, account.IsLive, symbol.QuoteAsset.AssetId, account.DepositAsset.AssetId); + + conversionSymbolModels = conversionLightSymbols.Select(iLightSymbol => account.Symbols.FirstOrDefault(iSymbol => iSymbol.Id == iLightSymbol.SymbolId)).Where(iSymbol => iSymbol is not null).ToArray(); + + conversionSymbolsCache[symbol.QuoteAsset.AssetId] = conversionSymbolModels; } + + symbol.ConversionSymbols.AddRange(conversionSymbolModels); } } diff --git a/samples/WPF.Sample/WPF.Sample.csproj b/samples/WPF.Sample/WPF.Sample.csproj index 373fc11..e5a846d 100644 --- a/samples/WPF.Sample/WPF.Sample.csproj +++ b/samples/WPF.Sample/WPF.Sample.csproj @@ -14,10 +14,12 @@ true + PreserveNewest PreserveNewest true + PreserveNewest PreserveNewest From b9cf94c41897754370c0670b01c88df8a17df396 Mon Sep 17 00:00:00 2001 From: toolsche Date: Fri, 12 Jun 2026 17:16:29 +0200 Subject: [PATCH 2/2] WPF sample: chart UX overhaul (pan/zoom, candle sizing) and restyle chart.html rewritten around data windowing: the financial plugin sizes candles from the number of points in the dataset, so it kept only a fixed-size window of candles on the chart while holding the full history in JS. This keeps candles a readable, consistent width at any history depth. Drag pans through time and price, dragging over the price axis scales it, double-click restores auto-fit, and the wheel zooms the time axis. The unreliable Hammer-based zoom plugin is no longer used. The price axis auto-fits the visible candles. - App.xaml/App.xaml.cs: switch the accent theme to Cobalt and initialize CefSharp with the GPU disabled (CefSharp 90 is unstable with hardware acceleration on modern Windows). - ShellView.xaml: larger, centered default window and a styled splitter. - ApiConfigurationView.xaml: roomier dialog with field watermarks. Stacked on the bug-fix PR; that one is needed for the chart to render on non-English locales, so the diff will shrink once it merges. Co-Authored-By: Claude Opus 4.8 --- samples/WPF.Sample/App.xaml | 2 +- samples/WPF.Sample/App.xaml.cs | 25 +- samples/WPF.Sample/Chart.js/chart.html | 260 ++++++++++++------ .../Views/ApiConfigurationView.xaml | 44 +-- samples/WPF.Sample/Views/ShellView.xaml | 18 +- 5 files changed, 237 insertions(+), 112 deletions(-) diff --git a/samples/WPF.Sample/App.xaml b/samples/WPF.Sample/App.xaml index 0a303af..dd67ae6 100644 --- a/samples/WPF.Sample/App.xaml +++ b/samples/WPF.Sample/App.xaml @@ -10,7 +10,7 @@ - + @@ -30,99 +31,186 @@ - \ No newline at end of file + diff --git a/samples/WPF.Sample/Views/ApiConfigurationView.xaml b/samples/WPF.Sample/Views/ApiConfigurationView.xaml index 720be08..70aaf66 100644 --- a/samples/WPF.Sample/Views/ApiConfigurationView.xaml +++ b/samples/WPF.Sample/Views/ApiConfigurationView.xaml @@ -14,8 +14,8 @@ mc:Ignorable="d"> @@ -27,50 +27,60 @@ - + - - + - + - +