diff --git a/Main.Test/App.xaml b/Main.Test/App.xaml
index e9613f0..da88b92 100644
--- a/Main.Test/App.xaml
+++ b/Main.Test/App.xaml
@@ -11,7 +11,7 @@
-
+
diff --git a/Main.Test/DemoData.cs b/Main.Test/DemoData.cs
new file mode 100644
index 0000000..6749173
--- /dev/null
+++ b/Main.Test/DemoData.cs
@@ -0,0 +1,78 @@
+using System.Globalization;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using Prism.Mvvm;
+
+namespace Main.Test;
+
+public class ItemInfo : BindableBase
+{
+ public int Code { get; init; }
+ public string Name { get; init; } = "";
+ public string Value => Price.ToString("N2", CultureInfo.CurrentCulture);
+ public decimal Price { get; init; }
+ public int Qty { get; init; }
+ public string Sku { get; init; } = "";
+ public string Shop { get; init; } = "";
+ public string Buyer { get; init; } = "";
+ public string Address { get; init; } = "";
+ public string Remark { get; init; } = "";
+ public DateTime CreatedAt { get; init; }
+ public ImageSource Thumbnail { get; init; } = null!;
+ public string? ThumbnailUrl { get; init; }
+ private string status = "待处理";
+ public string Status { get => status; set => SetProperty(ref status, value); }
+}
+
+internal static class DemoData
+{
+ // 冻结并共享小尺寸位图,滚动不触发网络请求或重复解码。
+ private static readonly ImageSource[] Thumbnails = CreateThumbnails();
+
+ public static List Create(int count, int networkImageSize = 0)
+ {
+ string[] names = ["轻量通勤双肩包", "纯棉宽松短袖", "旅行收纳组合", "便携保温水杯", "无线办公鼠标", "桌面收纳置物盒", "运动休闲鞋", "折叠防晒遮阳伞"];
+ string[] shops = ["城市生活旗舰店", "优选数码专营店", "日常好物集合店", "远行户外用品店"];
+ var random = new Random(20261022);
+ var session = Guid.NewGuid().ToString("N");
+ var result = new List(count);
+ for (var i = 0; i < count; i++)
+ result.Add(new ItemInfo
+ {
+ Code = 100000 + i, Name = names[i % names.Length] + " · 秋季新品 / 品质款",
+ Price = random.Next(1900, 69900) / 100m, Qty = random.Next(1, 100),
+ Sku = $"SKU-{i:000000} / {(i % 2 == 0 ? "雾蓝" : "米白")} / 标准款",
+ Shop = shops[i % shops.Length], Buyer = $"买家{i:00000}",
+ Address = $"浙江省杭州市西湖区 文一路 {i % 300 + 1} 号 · 联系尾号 {i % 10000:0000}",
+ Remark = i % 3 == 0 ? "请核对规格,周末可收货;外包装请加固。" : "常规发货 / 已核对收货信息",
+ CreatedAt = new DateTime(2026, 10, 22, 12, 0, 0).AddMinutes(-i),
+ Status = new[] { "待处理", "已确认", "待发货", "已完成" }[i % 4],
+ Thumbnail = Thumbnails[i % Thumbnails.Length],
+ ThumbnailUrl = networkImageSize == 0 ? null : $"https://picsum.photos/seed/{session}-{i}/{networkImageSize}/{networkImageSize}"
+ });
+ return result;
+ }
+
+ private static ImageSource[] CreateThumbnails()
+ {
+ var images = new ImageSource[16];
+ for (var i = 0; i < images.Length; i++)
+ {
+ var visual = new DrawingVisual();
+ using (var dc = visual.RenderOpen())
+ {
+ var color = Color.FromRgb((byte)(55 + i * 11), (byte)(100 + i * 6), (byte)(165 - i * 4));
+ dc.DrawRoundedRectangle(new SolidColorBrush(Color.FromRgb(241, 245, 249)), null, new Rect(0, 0, 72, 72), 8, 8);
+ var brush = new SolidColorBrush(color);
+ var pen = new Pen(brush, 4);
+ dc.DrawRoundedRectangle(null, pen, new Rect(25, 12, 22, 24), 10, 10);
+ dc.DrawRoundedRectangle(brush, null, new Rect(14, 26, 44, 36), 6, 6);
+ dc.DrawLine(new Pen(Brushes.White, 2), new Point(24, 44), new Point(48, 44));
+ }
+ var bitmap = new RenderTargetBitmap(72, 72, 96, 96, PixelFormats.Pbgra32);
+ bitmap.Render(visual); bitmap.Freeze(); images[i] = bitmap;
+ }
+ return images;
+ }
+}
diff --git a/Main.Test/MainWindow.xaml b/Main.Test/MainWindow.xaml
index 95d6a83..b17e2c0 100644
--- a/Main.Test/MainWindow.xaml
+++ b/Main.Test/MainWindow.xaml
@@ -3,59 +3,273 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:local="clr-namespace:Main.Test"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tf="Trade.Framework"
- xmlns:views="clr-namespace:Main.Test.Views"
xmlns:vm="clr-namespace:Main.Test.ViewModels"
- Title="MainWindow"
- Width="800"
- Height="450"
+ Title="复杂表格 · 运行测试"
+ Width="1380"
+ Height="820"
+ MinWidth="900"
+ MinHeight="500"
DataContext="{tf:DISource Type=vm:MainViewModel}"
- ResizeMode="CanResizeWithGrip"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ShowRowNumber="True"
+ ShowSelectAll="True">
+ Width="90"
+ Binding="{Binding Code}"
+ Header="编码" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+ Header="单价 / 元"
+ HeaderStyle="{DynamicResource DefaultNumberCellHeaderStyle}" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Main.Test/MainWindow.xaml.cs b/Main.Test/MainWindow.xaml.cs
index 345b2a5..a7cc606 100644
--- a/Main.Test/MainWindow.xaml.cs
+++ b/Main.Test/MainWindow.xaml.cs
@@ -1,35 +1,153 @@
-using Bogus;
-using System.Text;
+using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
+using System.Windows.Threading;
+using Main.Test.ViewModels;
using Trade.Framework.Controls;
namespace Main.Test;
-///
-/// Interaction logic for MainWindow.xaml
-///
+
public partial class MainWindow : WindowBase
{
- public MainWindow()
- {
- InitializeComponent();
- var mainHeaderImageContent = new MainHeaderImageContent();
- //NonClientAreaHeight = mainHeaderImageContent.Height;
- //NonClientAreaContent = mainHeaderImageContent;
- }
-}
+ public MainWindow()
+ {
+ InitializeComponent();
+ var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(500) };
+ timer.Tick += (_, _) => ImageStatisticsText.Text = ThumbnailLoader.Statistics;
+ Loaded += (_, _) => timer.Start();
+ Closed += (_, _) => timer.Stop();
+ }
-public class ItemInfo
-{
- public int Code { get; set; }
- public string Name { get; set; }
- public string Value { get; set; }
- public int Qty { get; set; }
-}
\ No newline at end of file
+ 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