4301f8a5f7
新建 DebugPanelLocalization.cs 集中维护中英文映射(类别 / 事件类型 / 字段名),显示用中文、内部匹配仍用英文标识,二者解耦。 涉及改动: 状态显示树:根节点 MotionState→运动状态、RaySourceState→射线源状态 等;字段 StageX→载物台X (μm) 等。StateNodeViewModel 新增 DisplayName,绑定到 UI;Name 保留英文供 FindNode 匹配 事件日志:EventLogEntry 新增 EventTypeDisplay/CategoryDisplay/FieldNameDisplay 派生属性;GridView 三列改绑中文;事件类型如 MotionStateChanged→运动状态变化,(No changes)→(无变化) 快照管理:详情树节点本地化 性能监控:PerformanceMetric 新增 StateTypeDisplay,状态类型列显示中文 快照差异窗口:标题 Snapshot Differences→快照差异对比,列头和按钮中文化 各处 MessageBox/文件对话框提示语全部翻译为中文 任务2 — 布局优化 主布局(顶部工具栏 / 左状态树 / 右上事件日志 / 右下快照·性能 Tab / 底部状态栏)符合"可视化查看系统状态与事件触发"的调试目标,结构保留。优化点:将顶部 ToolBarTray+ToolBar(带多余的拖动手柄和溢出箭头,即截图顶部那个浮动小工具条)替换为简洁的 Border+StackPanel,去掉无意义的拖拽/溢出交互。
253 lines
9.5 KiB
C#
253 lines
9.5 KiB
C#
using Prism.Mvvm;
|
|
using System;
|
|
using System.Collections.ObjectModel;
|
|
using System.Reflection;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Threading;
|
|
using XP.Common.Logging.Interfaces;
|
|
using XplorePlane.Models;
|
|
using XplorePlane.Services.AppState;
|
|
|
|
namespace XplorePlane.ViewModels.Debug
|
|
{
|
|
public class StateDisplayViewModel : BindableBase, IDisposable
|
|
{
|
|
private readonly IAppStateService _appStateService;
|
|
private readonly ILoggerService _logger;
|
|
private readonly Dispatcher _dispatcher;
|
|
private bool _initialized;
|
|
private bool _disposed;
|
|
|
|
public ObservableCollection<StateNodeViewModel> StateTree { get; } = new();
|
|
|
|
public StateDisplayViewModel(IAppStateService appStateService, ILoggerService loggerService, Dispatcher dispatcher)
|
|
{
|
|
_appStateService = appStateService ?? throw new ArgumentNullException(nameof(appStateService));
|
|
_logger = (loggerService ?? throw new ArgumentNullException(nameof(loggerService))).ForModule<StateDisplayViewModel>();
|
|
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
|
|
|
InitializeStateTree();
|
|
}
|
|
|
|
public void Initialize()
|
|
{
|
|
if (_initialized || _disposed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_appStateService.MotionStateChanged += OnMotionStateChanged;
|
|
_appStateService.RaySourceStateChanged += OnRaySourceStateChanged;
|
|
_appStateService.DetectorStateChanged += OnDetectorStateChanged;
|
|
_appStateService.SystemStateChanged += OnSystemStateChanged;
|
|
_appStateService.CameraStateChanged += OnCameraStateChanged;
|
|
_appStateService.LinkedViewStateChanged += OnLinkedViewStateChanged;
|
|
_appStateService.RecipeExecutionStateChanged += OnRecipeExecutionStateChanged;
|
|
|
|
ApplyStateSnapshot("MotionState", _appStateService.MotionState);
|
|
ApplyStateSnapshot("RaySourceState", _appStateService.RaySourceState);
|
|
ApplyStateSnapshot("DetectorState", _appStateService.DetectorState);
|
|
ApplyStateSnapshot("SystemState", _appStateService.SystemState);
|
|
ApplyStateSnapshot("CameraState", _appStateService.CameraState);
|
|
ApplyStateSnapshot("LinkedViewState", _appStateService.LinkedViewState);
|
|
ApplyStateSnapshot("RecipeExecutionState", _appStateService.RecipeExecutionState);
|
|
ApplyStateSnapshot("CalibrationMatrix", _appStateService.CalibrationMatrix);
|
|
|
|
_initialized = true;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_initialized)
|
|
{
|
|
_appStateService.MotionStateChanged -= OnMotionStateChanged;
|
|
_appStateService.RaySourceStateChanged -= OnRaySourceStateChanged;
|
|
_appStateService.DetectorStateChanged -= OnDetectorStateChanged;
|
|
_appStateService.SystemStateChanged -= OnSystemStateChanged;
|
|
_appStateService.CameraStateChanged -= OnCameraStateChanged;
|
|
_appStateService.LinkedViewStateChanged -= OnLinkedViewStateChanged;
|
|
_appStateService.RecipeExecutionStateChanged -= OnRecipeExecutionStateChanged;
|
|
}
|
|
|
|
_disposed = true;
|
|
}
|
|
|
|
private void InitializeStateTree()
|
|
{
|
|
AddRootNode("MotionState", typeof(MotionState));
|
|
AddRootNode("RaySourceState", typeof(RaySourceState));
|
|
AddRootNode("DetectorState", typeof(DetectorState));
|
|
AddRootNode("SystemState", typeof(SystemState));
|
|
AddRootNode("CameraState", typeof(CameraState));
|
|
AddRootNode("LinkedViewState", typeof(LinkedViewState));
|
|
AddRootNode("RecipeExecutionState", typeof(RecipeExecutionState));
|
|
AddRootNode("CalibrationMatrix", typeof(CalibrationMatrix));
|
|
}
|
|
|
|
private void AddRootNode(string category, Type stateType)
|
|
{
|
|
var root = new StateNodeViewModel
|
|
{
|
|
Name = category,
|
|
DisplayName = DebugPanelLocalization.Category(category),
|
|
Category = category
|
|
};
|
|
|
|
foreach (var property in stateType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
|
|
{
|
|
root.Children.Add(new StateNodeViewModel
|
|
{
|
|
Name = property.Name,
|
|
DisplayName = DebugPanelLocalization.Field(property.Name),
|
|
Category = category,
|
|
Value = string.Empty
|
|
});
|
|
}
|
|
|
|
StateTree.Add(root);
|
|
}
|
|
|
|
private void OnMotionStateChanged(object sender, StateChangedEventArgs<MotionState> e) => OnStateChanged("MotionState", e.OldValue, e.NewValue);
|
|
private void OnRaySourceStateChanged(object sender, StateChangedEventArgs<RaySourceState> e) => OnStateChanged("RaySourceState", e.OldValue, e.NewValue);
|
|
private void OnDetectorStateChanged(object sender, StateChangedEventArgs<DetectorState> e) => OnStateChanged("DetectorState", e.OldValue, e.NewValue);
|
|
private void OnSystemStateChanged(object sender, StateChangedEventArgs<SystemState> e) => OnStateChanged("SystemState", e.OldValue, e.NewValue);
|
|
private void OnCameraStateChanged(object sender, StateChangedEventArgs<CameraState> e) => OnStateChanged("CameraState", e.OldValue, e.NewValue);
|
|
private void OnLinkedViewStateChanged(object sender, StateChangedEventArgs<LinkedViewState> e) => OnStateChanged("LinkedViewState", e.OldValue, e.NewValue);
|
|
private void OnRecipeExecutionStateChanged(object sender, StateChangedEventArgs<RecipeExecutionState> e) => OnStateChanged("RecipeExecutionState", e.OldValue, e.NewValue);
|
|
|
|
private void OnStateChanged<T>(string category, T oldState, T newState)
|
|
{
|
|
try
|
|
{
|
|
_dispatcher.BeginInvoke(new Action(() =>
|
|
{
|
|
UpdateStateNodes(category, oldState, newState);
|
|
}));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Error(ex, "更新状态树失败 | Failed to update state tree for {Category}", category);
|
|
}
|
|
}
|
|
|
|
private void ApplyStateSnapshot<T>(string category, T state)
|
|
{
|
|
if (state == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UpdateStateNodes(category, default, state);
|
|
}
|
|
|
|
private void UpdateStateNodes<T>(string category, T oldState, T newState)
|
|
{
|
|
if (newState == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var property in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public))
|
|
{
|
|
var oldValue = oldState == null ? null : property.GetValue(oldState);
|
|
var newValue = property.GetValue(newState);
|
|
UpdateStateNode(category, property.Name, oldValue, newValue);
|
|
}
|
|
}
|
|
|
|
private void UpdateStateNode(string category, string fieldName, object oldValue, object newValue)
|
|
{
|
|
var node = FindNode(category, fieldName);
|
|
if (node == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
node.Value = DebugPanelStateFormatter.FormatValue(newValue);
|
|
|
|
if (!Equals(oldValue, newValue))
|
|
{
|
|
node.HighlightColor = DetermineHighlightColor(oldValue, newValue);
|
|
node.IsHighlighted = true;
|
|
_ = ClearHighlightAsync(node);
|
|
}
|
|
else
|
|
{
|
|
node.IsHighlighted = false;
|
|
node.HighlightColor = null;
|
|
}
|
|
}
|
|
|
|
private StateNodeViewModel FindNode(string category, string fieldName)
|
|
{
|
|
foreach (var root in StateTree)
|
|
{
|
|
if (!string.Equals(root.Name, category, StringComparison.Ordinal))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (var child in root.Children)
|
|
{
|
|
if (string.Equals(child.Name, fieldName, StringComparison.Ordinal))
|
|
{
|
|
return child;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private string DetermineHighlightColor(object oldValue, object newValue)
|
|
{
|
|
if (newValue is bool boolValue)
|
|
{
|
|
return boolValue ? "Green" : "Red";
|
|
}
|
|
|
|
var newType = newValue?.GetType();
|
|
if (newType != null && DebugPanelStateFormatter.IsNumeric(newType))
|
|
{
|
|
var oldNumber = oldValue == null ? 0d : Convert.ToDouble(oldValue);
|
|
var newNumber = Convert.ToDouble(newValue);
|
|
|
|
if (newNumber > oldNumber)
|
|
{
|
|
return "Green";
|
|
}
|
|
|
|
if (newNumber < oldNumber)
|
|
{
|
|
return "Red";
|
|
}
|
|
|
|
return "Yellow";
|
|
}
|
|
|
|
return "Yellow";
|
|
}
|
|
|
|
private async Task ClearHighlightAsync(StateNodeViewModel node)
|
|
{
|
|
await Task.Delay(TimeSpan.FromSeconds(2));
|
|
|
|
if (_disposed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_dispatcher.BeginInvoke(new Action(() =>
|
|
{
|
|
node.IsHighlighted = false;
|
|
node.HighlightColor = null;
|
|
}));
|
|
}
|
|
}
|
|
}
|