Files
XplorePlane/XplorePlane/ViewModels/Debug/StateDisplayViewModel.cs
T
zhengxuan.zhang 4301f8a5f7 任务1 — 调试窗体全中文化
新建 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,去掉无意义的拖拽/溢出交互。
2026-06-01 14:23:44 +08:00

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;
}));
}
}
}