From b12809bf34c9111e8f6511115dd9603176e287cc Mon Sep 17 00:00:00 2001 From: toolsche Date: Fri, 12 Jun 2026 17:16:10 +0200 Subject: [PATCH] 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