using SWRIS.Extensions; using SWRIS.Models; using SWRIS.Models.ViewModel; using SWRIS.Repository; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Threading; namespace SWRIS.Pages.RealTime { /// /// RealTimePage.xaml 的交互逻辑 /// public partial class RealTimePage : Page { /// 实时流超时隐藏阈值:超过该时长未收到新数据则隐藏实时值 private const long LiveStreamTimeoutTicks = TimeSpan.TicksPerSecond; private readonly IRecordRepository _recordRepository; /// 全部设备(小车过滤的原始全集) private readonly ObservableCollection _allEquipments; private readonly DispatcherTimer _liveStreamTimer; /// 记录各设备最后一次收到实时数据的时间(UtcNow.Ticks),超过 1 秒未更新时隐藏 private readonly Dictionary _lastLiveStreamTimes = new Dictionary(); public RealTimeViewModel ViewModel { get; set; } public RealTimePage() { InitializeComponent(); _recordRepository = new RecordRepository(); ViewModel = new RealTimeViewModel(_recordRepository.GetLastRecords().ToSimpleRecordDtos()); _allEquipments = ViewModel.Equipments; FilterEquipmentsByTrolley(ViewModel.Trolleys.FirstOrDefault()); DataContext = ViewModel; // 定时从各设备的实时流队列取出一项(取出即移出)填充 LiveStreamValues,供设备卡片显示 _liveStreamTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(50) }; _liveStreamTimer.Tick += LiveStreamTimer_Tick; _liveStreamTimer.Start(); Unloaded += RealTimePage_Unloaded; } private void RealTimePage_Unloaded(object sender, RoutedEventArgs e) { _liveStreamTimer.Stop(); } /// /// 从 LiveStreams 队列取出一项(取出即移出)并显示各传感器值;设备离线时不显示, /// 超过 1 秒未收到新数据则隐藏实时值,值未变化时跳过更新 /// private void LiveStreamTimer_Tick(object sender, EventArgs e) { var equipments = ViewModel?.Equipments; if (equipments == null) return; // 用 UtcNow.Ticks 作为单调时钟,避免 DateTime.Now 的时区/日历换算开销 long now = DateTime.UtcNow.Ticks; foreach (var equipmentData in equipments) { UpdateEquipmentLiveStream(equipmentData, now); } } private void UpdateEquipmentLiveStream(EquipmentDataModel equipmentData, long now) { var values = equipmentData.LiveStreamValues; // 设备离线时不显示 if (!equipmentData.IsConnect) { ClearValues(values); return; } // 超过 1 秒未收到新数据 → 隐藏实时值(首次未见数据时 last 为 0,直接隐藏) if (!_lastLiveStreamTimes.TryGetValue(equipmentData, out long last) || now - last > LiveStreamTimeoutTicks) ClearValues(values); // 无新数据:保持当前显示状态 if (!equipmentData.TryDequeueLiveStream(out int[] sample)) return; // 有新数据,记录更新时间 _lastLiveStreamTimes[equipmentData] = now; // 值未变化则跳过,减少无谓的集合通知与 UI 重绘 if (ValuesEqual(values, sample)) return; // 用索引器赋值(触发 Replace 通知,UI 容器复用)代替 Clear+Add 全量重建,减轻闪烁 ApplySample(values, sample); } /// 比较当前显示值与新样本是否一致(长度与逐项内容均相同) private static bool ValuesEqual(ObservableCollection values, int[] sample) { if (values.Count != sample.Length) return false; for (int i = 0; i < sample.Length; i++) { if (values[i] != sample[i]) return false; } return true; } /// 用新样本更新集合:已有项索引器赋值(容器复用),数量差异用最小化 Add/RemoveAt 补齐 private static void ApplySample(ObservableCollection values, int[] sample) { int common = Math.Min(values.Count, sample.Length); for (int i = 0; i < common; i++) { values[i] = sample[i]; } while (values.Count > sample.Length) { values.RemoveAt(values.Count - 1); } while (values.Count < sample.Length) { values.Add(sample[values.Count]); } } /// 仅在集合非空时清空,避免无谓的集合通知触发 UI 重绘 private static void ClearValues(ObservableCollection values) { if (values.Count > 0) values.Clear(); } private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if ((sender as Border)?.DataContext is TrolleyActiveModel activedTrolley) { ViewModel.ActivedTrolly = activedTrolley; FilterEquipmentsByTrolley(activedTrolley); } } private void FilterEquipmentsByTrolley(TrolleyActiveModel trolley) { if (trolley == null) { ViewModel.Equipments = _allEquipments; return; } var filtered = _allEquipments .Where(e => e.TrolleyId == trolley.Id) .ConvertToObservableCollection(); ViewModel.Equipments = filtered; } } }