using System.Diagnostics; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; using Main.Test.ViewModels; using Trade.Framework.Controls; namespace Main.Test; public partial class MainWindow : WindowBase { public MainWindow() { InitializeComponent(); var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(500) }; timer.Tick += (_, _) => ImageStatisticsText.Text = ThumbnailLoader.Statistics; Loaded += (_, _) => timer.Start(); Closed += (_, _) => timer.Stop(); } private async void LoadData_Click(object sender, RoutedEventArgs e) { SetBusy(true); try { var count = int.Parse((string)((ComboBoxItem)DataSize.SelectedItem).Tag); var watch = Stopwatch.StartNew(); var vm = (MainViewModel)DataContext; vm.NetworkImageSize = int.Parse((string)((ComboBoxItem)ImageMode.SelectedItem).Tag); await vm.LoadAsync(count); OrdersGrid.ScrollIntoView(OrdersGrid.Items[0]); await Dispatcher.Yield(DispatcherPriority.ApplicationIdle); PerformanceText.Text = $"加载 {count:N0} 行(含生成与首次布局):{watch.Elapsed.TotalMilliseconds:N0} ms。可继续测试滚动和全选。"; } catch (Exception ex) { PerformanceText.Text = $"加载失败:{ex.Message}"; } finally { SetBusy(false); } } private async void Benchmark_Click(object sender, RoutedEventArgs e) { SetBusy(true); try { PerformanceText.Text = await RunPerformanceTestAsync(); } catch (Exception ex) { PerformanceText.Text = $"测试失败:{ex.Message}"; } finally { SetBusy(false); } } private async void ImageBenchmark_Click(object sender, RoutedEventArgs e) { SetBusy(true); try { PerformanceText.Text = await RunImagePerformanceTestAsync(); } catch (Exception ex) { PerformanceText.Text = $"图片测试失败:{ex.Message}"; } finally { SetBusy(false); } } public async Task RunImagePerformanceTestAsync() { if (OrdersGrid.Items.Count == 0 || ((MainViewModel)DataContext).NetworkImageSize == 0) return "请先选择 Picsum 图片模式并点击加载数据,再运行图片滚动测试。"; var viewer = FindVisual(OrdersGrid).First(); var offset = viewer.VerticalOffset; var samples = new[] { new List(), new List() }; var heartbeat = Stopwatch.StartNew(); var lastTick = heartbeat.Elapsed.TotalMilliseconds; var maxDelay = 0d; var pulse = new DispatcherTimer(DispatcherPriority.Normal) { Interval = TimeSpan.FromMilliseconds(50) }; pulse.Tick += (_, _) => { var now = heartbeat.Elapsed.TotalMilliseconds; maxDelay = Math.Max(maxDelay, Math.Max(0, now - lastTick - 50)); lastTick = now; }; pulse.Start(); try { viewer.ScrollToHorizontalOffset(0); for (var pass = 0; pass < 2; pass++) { PerformanceText.Text = pass == 0 ? "首次滚动:网络图片正在加载…" : "重复访问相同位置:比较缓存与重新加载…"; for (var i = 0; i < 40; i++) { var watch = Stopwatch.StartNew(); viewer.ScrollToVerticalOffset(i % 24); await Dispatcher.Yield(DispatcherPriority.ApplicationIdle); samples[pass].Add(watch.Elapsed.TotalMilliseconds); await Task.Delay(150); } } // 有界等待当前可见图片,避免把尚未完成的请求当作加载成功。 var wait = Stopwatch.StartNew(); while (ThumbnailLoader.Pending > 0 && wait.Elapsed < TimeSpan.FromSeconds(8)) await Task.Delay(100); var first = samples[0].Order().ToArray(); var revisit = samples[1].Order().ToArray(); return $"Picsum {((MainViewModel)DataContext).NetworkImageSize}px / {OrdersGrid.Items.Count:N0} 行 | 首次滚动均值 {first.Average():N1} ms / P95 {first[37]:N1} ms | 复访均值 {revisit.Average():N1} ms / P95 {revisit[37]:N1} ms | UI 心跳最大延迟 {maxDelay:N1} ms(非帧率);图片统计为本次进程累计,取消表示滚出视口。"; } finally { pulse.Stop(); viewer.ScrollToVerticalOffset(offset); } } // 测量 UI 线程滚动与布局响应,不等同于 GPU 帧率。 public async Task RunPerformanceTestAsync() { if (OrdersGrid.Items.Count == 0) return "没有可测试的数据。"; var viewer = FindVisual(OrdersGrid).First(); var offset = viewer.VerticalOffset; var selected = ((DataGrid)OrdersGrid).SelectedItems.Cast().ToArray(); var samples = new List(); try { OrdersGrid.UnselectAll(); OrdersGrid.ScrollIntoView(OrdersGrid.Items[0]); viewer.ScrollToHorizontalOffset(0); await Dispatcher.Yield(DispatcherPriority.ApplicationIdle); for (var i = 0; i < 60; i++) { var watch = Stopwatch.StartNew(); viewer.ScrollToVerticalOffset(viewer.ScrollableHeight * ((i * 17) % 60) / 59d); await Dispatcher.Yield(DispatcherPriority.ApplicationIdle); samples.Add(watch.Elapsed.TotalMilliseconds); } var selectWatch = Stopwatch.StartNew(); OrdersGrid.SelectAll(); await Dispatcher.Yield(DispatcherPriority.ApplicationIdle); var selectMs = selectWatch.Elapsed.TotalMilliseconds; selectWatch.Restart(); OrdersGrid.UnselectAll(); await Dispatcher.Yield(DispatcherPriority.ApplicationIdle); var clearMs = selectWatch.Elapsed.TotalMilliseconds; var realized = FindVisual(OrdersGrid).Count(); samples.Sort(); return $"{OrdersGrid.Items.Count:N0} 行 / 9 列 | 滚动与布局:均值 {samples.Average():N1} ms,P95 {samples[56]:N1} ms,最大 {samples[^1]:N1} ms | 全选 {selectMs:N0} ms / 取消 {clearMs:N0} ms | 已生成行 {realized} | 进程内存 {Process.GetCurrentProcess().WorkingSet64 / 1024 / 1024} MB(不等同帧率)"; } finally { OrdersGrid.RestoreSelection(selected); viewer.ScrollToVerticalOffset(offset); } } private void SetBusy(bool busy) { LoadButton.IsEnabled = BenchmarkButton.IsEnabled = ImageBenchmarkButton.IsEnabled = DataSize.IsEnabled = ImageMode.IsEnabled = !busy; OrdersGrid.IsEnabled = !busy; } private static IEnumerable FindVisual(DependencyObject parent) where T : DependencyObject { for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) { var child = VisualTreeHelper.GetChild(parent, i); if (child is T match) yield return match; foreach (var descendant in FindVisual(child)) yield return descendant; } } }