using System.Windows; using System.Windows.Media; using System.Windows.Threading; using Pcie8586Probe.Models; using Pcie8586Probe.ViewModels; namespace Pcie8586Probe; public partial class MainWindow : Window { private const double TableRowHeight = 20.0; private const double TableHeaderHeight = 24.0; private readonly MainViewModel _viewModel = new(); private readonly DispatcherTimer _renderTimer; public MainWindow() { InitializeComponent(); DataContext = _viewModel; _renderTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(50) }; _renderTimer.Tick += RenderTimer_Tick; _renderTimer.Start(); ConfigurePlot(); } private void ConfigurePlot() { var fontName = GetChineseFontName(); WavePlot.Plot.Font.Set(fontName); WavePlot.Plot.Title("PCIe8586M \u5B9E\u65F6\u6CE2\u5F62"); WavePlot.Plot.XLabel("\u6837\u672C\u5E8F\u53F7"); WavePlot.Plot.YLabel("\u7535\u538B (V)"); WavePlot.Plot.Axes.SetLimitsY(-5.2, 5.2); WavePlot.Refresh(); } private void RenderTimer_Tick(object? sender, EventArgs e) { if (_viewModel.ConsumePlotClearRequest()) { ClearPlot(); } var frame = _viewModel.ConsumeLatestFrame(); if (frame is not null) { _viewModel.AppendTableRows(frame, GetVisibleTableRowCount()); } var plotBlock = _viewModel.ConsumePendingPlotBlock(); if (plotBlock is not null) { DrawFrame(plotBlock, _viewModel.ConfiguredRange); } } private void ClearPlot() { WavePlot.Plot.Clear(); ConfigurePlot(); } private void DrawFrame(SampleBlock frame, InputRange range) { WavePlot.Plot.Clear(); var xLimits = GetDisplayX(frame); for (var channel = 0; channel < frame.ChannelCount; channel++) { var (xs, ys) = Downsample(frame.Channels[channel], frame.StartSampleIndex, 6_000); var scatter = WavePlot.Plot.Add.Scatter(xs, ys); scatter.LegendText = $"CH{channel}"; } WavePlot.Plot.Axes.SetLimitsX(xLimits.Min, xLimits.Max); WavePlot.Plot.Axes.SetLimitsY( range == InputRange.PlusMinus5V ? -5.2 : -1.2, range == InputRange.PlusMinus5V ? 5.2 : 1.2); WavePlot.Plot.ShowLegend(); WavePlot.Refresh(); } private static (double[] Xs, double[] Ys) Downsample(double[] source, long startSampleIndex, int maxPoints) { if (source.Length <= maxPoints) { var xs = new double[source.Length]; for (var index = 0; index < source.Length; index++) { xs[index] = startSampleIndex + index; } return (xs, source); } var xsDownsampled = new double[maxPoints]; var ys = new double[maxPoints]; var step = source.Length / (double)maxPoints; for (var index = 0; index < maxPoints; index++) { var sourceIndex = (int)(index * step); xsDownsampled[index] = startSampleIndex + sourceIndex; ys[index] = source[sourceIndex]; } return (xsDownsampled, ys); } private static (double Min, double Max) GetDisplayX(SampleBlock frame) { var min = frame.StartSampleIndex; var max = frame.StartSampleIndex + Math.Max(1, frame.SamplesPerChannel - 1); return (min, max); } private int GetVisibleTableRowCount() { var usableHeight = Math.Max(0, SampleTable.ActualHeight - TableHeaderHeight - 2); return Math.Max(1, (int)Math.Ceiling(usableHeight / TableRowHeight)); } private static string GetChineseFontName() { string[] candidates = [ "Microsoft YaHei UI", "Microsoft YaHei", "SimSun", "Noto Sans CJK SC", "Arial Unicode MS" ]; var installed = Fonts.SystemFontFamilies.Select(static family => family.Source).ToHashSet(StringComparer.OrdinalIgnoreCase); return candidates.FirstOrDefault(installed.Contains) ?? "Segoe UI"; } private void Window_Closing(object? sender, System.ComponentModel.CancelEventArgs e) { _renderTimer.Stop(); _viewModel.Dispose(); } }