RealTimePage.xaml.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. using SWRIS.Extensions;
  2. using SWRIS.Models;
  3. using SWRIS.Models.ViewModel;
  4. using SWRIS.Repository;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Collections.ObjectModel;
  8. using System.Linq;
  9. using System.Windows;
  10. using System.Windows.Controls;
  11. using System.Windows.Input;
  12. using System.Windows.Threading;
  13. namespace SWRIS.Pages.RealTime
  14. {
  15. /// <summary>
  16. /// RealTimePage.xaml 的交互逻辑
  17. /// </summary>
  18. public partial class RealTimePage : Page
  19. {
  20. /// <summary>实时流超时隐藏阈值:超过该时长未收到新数据则隐藏实时值</summary>
  21. private const long LiveStreamTimeoutTicks = TimeSpan.TicksPerSecond;
  22. private readonly IRecordRepository _recordRepository;
  23. /// <summary>全部设备(小车过滤的原始全集)</summary>
  24. private readonly ObservableCollection<EquipmentDataModel> _allEquipments;
  25. private readonly DispatcherTimer _liveStreamTimer;
  26. /// <summary>记录各设备最后一次收到实时数据的时间(UtcNow.Ticks),超过 1 秒未更新时隐藏</summary>
  27. private readonly Dictionary<EquipmentDataModel, long> _lastLiveStreamTimes = new Dictionary<EquipmentDataModel, long>();
  28. public RealTimeViewModel ViewModel { get; set; }
  29. public RealTimePage()
  30. {
  31. InitializeComponent();
  32. _recordRepository = new RecordRepository();
  33. ViewModel = new RealTimeViewModel(_recordRepository.GetLastRecords().ToSimpleRecordDtos());
  34. _allEquipments = ViewModel.Equipments;
  35. FilterEquipmentsByTrolley(ViewModel.Trolleys.FirstOrDefault());
  36. DataContext = ViewModel;
  37. // 定时从各设备的实时流队列取出一项(取出即移出)填充 LiveStreamValues,供设备卡片显示
  38. _liveStreamTimer = new DispatcherTimer
  39. {
  40. Interval = TimeSpan.FromMilliseconds(50)
  41. };
  42. _liveStreamTimer.Tick += LiveStreamTimer_Tick;
  43. _liveStreamTimer.Start();
  44. Unloaded += RealTimePage_Unloaded;
  45. }
  46. private void RealTimePage_Unloaded(object sender, RoutedEventArgs e)
  47. {
  48. _liveStreamTimer.Stop();
  49. }
  50. /// <summary>
  51. /// 从 LiveStreams 队列取出一项(取出即移出)并显示各传感器值;设备离线时不显示,
  52. /// 超过 1 秒未收到新数据则隐藏实时值,值未变化时跳过更新
  53. /// </summary>
  54. private void LiveStreamTimer_Tick(object sender, EventArgs e)
  55. {
  56. var equipments = ViewModel?.Equipments;
  57. if (equipments == null)
  58. return;
  59. // 用 UtcNow.Ticks 作为单调时钟,避免 DateTime.Now 的时区/日历换算开销
  60. long now = DateTime.UtcNow.Ticks;
  61. foreach (var equipmentData in equipments)
  62. {
  63. UpdateEquipmentLiveStream(equipmentData, now);
  64. }
  65. }
  66. private void UpdateEquipmentLiveStream(EquipmentDataModel equipmentData, long now)
  67. {
  68. var values = equipmentData.LiveStreamValues;
  69. // 设备离线时不显示
  70. if (!equipmentData.IsConnect)
  71. {
  72. ClearValues(values);
  73. return;
  74. }
  75. // 超过 1 秒未收到新数据 → 隐藏实时值(首次未见数据时 last 为 0,直接隐藏)
  76. if (!_lastLiveStreamTimes.TryGetValue(equipmentData, out long last) || now - last > LiveStreamTimeoutTicks)
  77. ClearValues(values);
  78. // 无新数据:保持当前显示状态
  79. if (!equipmentData.TryDequeueLiveStream(out int[] sample))
  80. return;
  81. // 有新数据,记录更新时间
  82. _lastLiveStreamTimes[equipmentData] = now;
  83. // 值未变化则跳过,减少无谓的集合通知与 UI 重绘
  84. if (ValuesEqual(values, sample))
  85. return;
  86. // 用索引器赋值(触发 Replace 通知,UI 容器复用)代替 Clear+Add 全量重建,减轻闪烁
  87. ApplySample(values, sample);
  88. }
  89. /// <summary>比较当前显示值与新样本是否一致(长度与逐项内容均相同)</summary>
  90. private static bool ValuesEqual(ObservableCollection<int> values, int[] sample)
  91. {
  92. if (values.Count != sample.Length)
  93. return false;
  94. for (int i = 0; i < sample.Length; i++)
  95. {
  96. if (values[i] != sample[i])
  97. return false;
  98. }
  99. return true;
  100. }
  101. /// <summary>用新样本更新集合:已有项索引器赋值(容器复用),数量差异用最小化 Add/RemoveAt 补齐</summary>
  102. private static void ApplySample(ObservableCollection<int> values, int[] sample)
  103. {
  104. int common = Math.Min(values.Count, sample.Length);
  105. for (int i = 0; i < common; i++)
  106. {
  107. values[i] = sample[i];
  108. }
  109. while (values.Count > sample.Length)
  110. {
  111. values.RemoveAt(values.Count - 1);
  112. }
  113. while (values.Count < sample.Length)
  114. {
  115. values.Add(sample[values.Count]);
  116. }
  117. }
  118. /// <summary>仅在集合非空时清空,避免无谓的集合通知触发 UI 重绘</summary>
  119. private static void ClearValues(ObservableCollection<int> values)
  120. {
  121. if (values.Count > 0)
  122. values.Clear();
  123. }
  124. private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
  125. {
  126. if ((sender as Border)?.DataContext is TrolleyActiveModel activedTrolley)
  127. {
  128. ViewModel.ActivedTrolly = activedTrolley;
  129. FilterEquipmentsByTrolley(activedTrolley);
  130. }
  131. }
  132. private void FilterEquipmentsByTrolley(TrolleyActiveModel trolley)
  133. {
  134. if (trolley == null)
  135. {
  136. ViewModel.Equipments = _allEquipments;
  137. return;
  138. }
  139. var filtered = _allEquipments
  140. .Where(e => e.TrolleyId == trolley.Id)
  141. .ConvertToObservableCollection();
  142. ViewModel.Equipments = filtered;
  143. }
  144. }
  145. }