添加项目文件。
This commit is contained in:
302
MainModule/ViewModels/AutomatedTestingViewModel.cs
Normal file
302
MainModule/ViewModels/AutomatedTestingViewModel.cs
Normal file
@@ -0,0 +1,302 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows.Input;
|
||||
using DeviceCommand.Devices;
|
||||
using Logger;
|
||||
using Prism.Ioc;
|
||||
using TestingModule.ViewModels;
|
||||
using UIShare;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.PubEvent;
|
||||
using UIShare.UIViewModel;
|
||||
using UIShare.ViewModelBase;
|
||||
using static System.Formats.Asn1.AsnWriter;
|
||||
|
||||
namespace MainModule.ViewModels
|
||||
{
|
||||
public class AutomatedTestingViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
||||
{
|
||||
#region 私有字段
|
||||
private string _testStatus;
|
||||
private readonly IScopedProvider _scope;
|
||||
private bool IsInitialized =false;
|
||||
private bool _isRunningErrorSteps = false;
|
||||
private SubscriptionToken? _alarmToken;
|
||||
#endregion
|
||||
|
||||
#region 属性
|
||||
public bool KeepAlive => true; // 保持存活
|
||||
|
||||
public string TestStatus
|
||||
{
|
||||
get => _testStatus;
|
||||
set => SetProperty(ref _testStatus, value);
|
||||
}
|
||||
|
||||
// 该 AutomatedTestingView 实例独占的 ScopedContext,5 个子面板共享
|
||||
public ScopedContext _scopedContext { get; }
|
||||
public StepRunning _stepRunning { get; }
|
||||
public GlobalInfo _globalInfo { get; }
|
||||
public SystemConfig _systemConfig { get; set; }
|
||||
public DeviceManager _deviceManager { get; set; }
|
||||
|
||||
// 5 个子 ViewModel 全部从同一个 scope 解析,自动注入同一个 ScopedContext
|
||||
public CommandTreeViewModel CommandTreeVM { get; }
|
||||
public StepsManagerViewModel StepsManagerVM { get; }
|
||||
public SingleStepEditViewModel SingleStepEditVM { get; }
|
||||
public LogAreaViewModel LogAreaVM { get; }
|
||||
public ParametersManagerViewModel ParametersManagerVM { get; }
|
||||
#endregion
|
||||
|
||||
public ICommand RefreshCommand { get; set; }
|
||||
public ICommand BackToProtocolCommand { get; set; }
|
||||
public ICommand LoadedCommand { get; set; }
|
||||
|
||||
public AutomatedTestingViewModel(IContainerExtension container) : base(container)
|
||||
{
|
||||
// 每个 AutomatedTestingViewModel 实例创建独立的容器作用域
|
||||
_scope = container.CreateScope();
|
||||
_globalInfo =container.Resolve<GlobalInfo>();
|
||||
_systemConfig = _scope.Resolve<SystemConfig>();
|
||||
//加载Json数据
|
||||
if (ConfigService.IsExit(_globalInfo.CurrentOpeningScope))
|
||||
{
|
||||
string filePath = System.IO.Path.Combine(_systemConfig.SystemPath, $"{_globalInfo.CurrentOpeningScope}.json");
|
||||
if (System.IO.File.Exists(filePath))
|
||||
{
|
||||
string json = System.IO.File.ReadAllText(filePath);
|
||||
Newtonsoft.Json.JsonConvert.PopulateObject(json, _systemConfig);
|
||||
}
|
||||
}
|
||||
//容器解析顺序不要改变!!!
|
||||
_scopedContext = _scope.Resolve<ScopedContext>();
|
||||
_deviceManager = _scope.Resolve<DeviceManager>();
|
||||
_stepRunning = _scope.Resolve<StepRunning>();
|
||||
// 从同一个 _scope 解析 5 个子 VM,DI 会把同一个 ScopedContext 注入它们
|
||||
CommandTreeVM = _scope.Resolve<CommandTreeViewModel>();
|
||||
StepsManagerVM = _scope.Resolve<StepsManagerViewModel>();
|
||||
SingleStepEditVM = _scope.Resolve<SingleStepEditViewModel>();
|
||||
LogAreaVM = _scope.Resolve<LogAreaViewModel>();
|
||||
ParametersManagerVM = _scope.Resolve<ParametersManagerViewModel>();
|
||||
RefreshCommand = new DelegateCommand(OnRefresh);
|
||||
BackToProtocolCommand = new DelegateCommand(OnBackToProtocol);
|
||||
LoadedCommand = new AsyncDelegateCommand(OnLoad);
|
||||
_eventAggregator.GetEvent<SilenceBuzzerEvent>().Subscribe(async (scope) => await SilenceBuzzer(scope));
|
||||
_alarmToken = _eventAggregator.GetEvent<AlarmEvent>().Subscribe(OnAlarmTriggered);
|
||||
}
|
||||
|
||||
private async Task SilenceBuzzer(string scope)
|
||||
{
|
||||
if (_deviceManager.IOGroup == null) return;
|
||||
|
||||
if (scope == "default")
|
||||
{
|
||||
for(int i = 1; i <= 8; i++)
|
||||
{
|
||||
await _deviceManager.IOGroup.执行台架动作Async(i, 功能动作.蜂鸣器报警, false);
|
||||
}
|
||||
}
|
||||
else if (scope == TestStatus)
|
||||
{
|
||||
int 台架号 = _systemConfig.SharedParameterList
|
||||
.FirstOrDefault(x => x.ParameterName == "台架序号")?.Value ?? 0;
|
||||
if (台架号 >= 1 && 台架号 <= 8)
|
||||
{
|
||||
await _deviceManager.IOGroup.执行台架动作Async(台架号, 功能动作.蜂鸣器报警, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AlarmEvent 回调:
|
||||
/// 上下限 → 蜂鸣器报警 true
|
||||
/// 上下极限 → 蜂鸣器报警 true + 执行对应 scope 的异常流程
|
||||
/// </summary>
|
||||
private async void OnAlarmTriggered((string Scope, string Fingerprint, string Status) args)
|
||||
{
|
||||
if (args.Scope != TestStatus) return;
|
||||
if (_deviceManager?.IOGroup == null) return;
|
||||
|
||||
int 台架号 = _systemConfig.SharedParameterList
|
||||
.FirstOrDefault(x => x.ParameterName == "台架序号")?.Value ?? 0;
|
||||
if (台架号 < 1 || 台架号 > 8) return;
|
||||
|
||||
// 上下限 / 上下极限 均触发蜂鸣器报警
|
||||
await _deviceManager.IOGroup.执行台架动作Async(台架号, 功能动作.蜂鸣器报警, true);
|
||||
LoggerHelper.WarnWithNotify(args.Scope, $"[{args.Scope}] 报警触发: {args.Status} ({args.Fingerprint}),蜂鸣器已开启。");
|
||||
|
||||
// 上下极限额外触发异常流程
|
||||
if (args.Status == "超上极限" || args.Status == "超下极限")
|
||||
{
|
||||
_ = TriggerErrorStepsAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行异常流程(带并发保护,同一时间仅允许一次)。
|
||||
/// 如果正常流程正在运行,先取消并等待其停止,再执行异常流程。
|
||||
/// </summary>
|
||||
private async Task TriggerErrorStepsAsync()
|
||||
{
|
||||
if (_isRunningErrorSteps) return;
|
||||
_isRunningErrorSteps = true;
|
||||
try
|
||||
{
|
||||
// 1. 如果正常流程正在运行,先停止它
|
||||
// IsStop == false 表示正在运行;IsStop == null 表示未启动或已结束
|
||||
if (_scopedContext.IsStop == false)
|
||||
{
|
||||
LoggerHelper.WarnWithNotify(TestStatus, $"[{TestStatus}] 报警触发,正在停止正常流程...");
|
||||
_stepRunning.stepCTS.Cancel();
|
||||
|
||||
// 等待 ShellViewModel 清理完毕(IsStop 变回 null、stepCTS 被重置)
|
||||
// ExecuteMethodStep 内部硬编码使用 stepCTS.Token,必须等它重置后才能跑异常流程
|
||||
var deadline = DateTime.Now.AddSeconds(5);
|
||||
while ((_scopedContext.IsStop != null || _stepRunning.stepCTS.IsCancellationRequested)
|
||||
&& DateTime.Now < deadline)
|
||||
{
|
||||
await Task.Delay(50);
|
||||
}
|
||||
|
||||
// 超时后仍未重置则强制重置,确保异常流程的设备命令能拿到有效 Token
|
||||
if (_stepRunning.stepCTS.IsCancellationRequested)
|
||||
{
|
||||
_stepRunning.stepCTS = new CancellationTokenSource();
|
||||
_scopedContext.IsStop = null;
|
||||
_scopedContext.RunState = "运行";
|
||||
}
|
||||
|
||||
LoggerHelper.WarnWithNotify(TestStatus, $"[{TestStatus}] 正常流程已停止,开始执行异常流程");
|
||||
}
|
||||
|
||||
// 2. 执行异常流程
|
||||
await _stepRunning.ExecuteErrorSteps(
|
||||
_scopedContext.Program,
|
||||
cancellationToken: _stepRunning.errorStepCTS.Token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify(TestStatus, $"[{TestStatus}] 报警触发异常流程失败: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isRunningErrorSteps = false;
|
||||
if (_stepRunning.errorStepCTS.IsCancellationRequested)
|
||||
_stepRunning.errorStepCTS = new CancellationTokenSource();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// 1. 如果 TestStatus 为空,说明还没走到 OnNavigatedTo 赋值,直接释放 scope 即可
|
||||
if (string.IsNullOrEmpty(TestStatus))
|
||||
{
|
||||
_scope?.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_alarmToken != null)
|
||||
_eventAggregator.GetEvent<AlarmEvent>().Unsubscribe(_alarmToken);
|
||||
_eventAggregator.GetEvent<SilenceBuzzerEvent>().Unsubscribe(async (scope) => await SilenceBuzzer(scope));
|
||||
// 2. 显式释放硬件资源(防止端口占用/死锁)
|
||||
if (_deviceManager is IDisposable disposableDevice)
|
||||
{
|
||||
disposableDevice.Dispose();
|
||||
}
|
||||
(CommandTreeVM as IDisposable)?.Dispose();
|
||||
(StepsManagerVM as IDisposable)?.Dispose();
|
||||
(SingleStepEditVM as IDisposable)?.Dispose();
|
||||
(LogAreaVM as IDisposable)?.Dispose();
|
||||
(ParametersManagerVM as IDisposable)?.Dispose();
|
||||
|
||||
_globalInfo.ContextDic?.Remove(TestStatus);
|
||||
_globalInfo.StepRunningDic?.Remove(TestStatus);
|
||||
_globalInfo.ConfigDic?.Remove(TestStatus);
|
||||
_globalInfo.ScopeDic?.Remove(TestStatus);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LoggerHelper.ErrorWithNotify(TestStatus, $"卸载机台 [{TestStatus}] 全局引用或资源失败: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_scope?.Dispose();
|
||||
}
|
||||
}
|
||||
#region 命令处理与事件
|
||||
private async Task OnLoad()
|
||||
{
|
||||
if (!IsInitialized)
|
||||
{
|
||||
await _deviceManager.ConnectAllDevices();
|
||||
IsInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRefresh()
|
||||
{
|
||||
// 双击:把自己的名字扔出去
|
||||
_globalInfo.CurrentScope = TestStatus;
|
||||
_eventAggregator.GetEvent<ExpandViewEvent>().Publish(TestStatus);
|
||||
|
||||
}
|
||||
|
||||
private void OnBackToProtocol()
|
||||
{
|
||||
// 返回:扔个空字符串出去
|
||||
_eventAggregator.GetEvent<ExpandViewEvent>().Publish("");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 重写
|
||||
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||
{
|
||||
base.OnNavigatedTo(navigationContext);
|
||||
if (navigationContext.Parameters.ContainsKey("Name"))
|
||||
{
|
||||
TestStatus = navigationContext.Parameters.GetValue<string>("Name");
|
||||
_globalInfo.ContextDic.Add(TestStatus, _scopedContext);
|
||||
_globalInfo.StepRunningDic.Add(TestStatus, _stepRunning);
|
||||
_globalInfo.ScopeDic.Add(TestStatus, _scope);
|
||||
_globalInfo.ConfigDic.Add(TestStatus, _systemConfig);
|
||||
if(_systemConfig.DefaultProgramFilePath != null&&File.Exists(_systemConfig.DefaultProgramFilePath))
|
||||
{
|
||||
var filePath = _systemConfig.DefaultProgramFilePath;
|
||||
_systemConfig.CurrentACPFile= _systemConfig.DefaultProgramFilePath;
|
||||
// 读取 JSON 文件
|
||||
string json = File.ReadAllText(filePath);
|
||||
|
||||
// 反序列化为 ProgramVM
|
||||
var program = Newtonsoft.Json.JsonConvert.DeserializeObject<ProgramVM>(json);
|
||||
|
||||
if (program == null)
|
||||
{
|
||||
LoggerHelper.WarnWithNotify(_globalInfo.CurrentScope, $"文件格式不正确或为空: {filePath}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 💡 2. 严格赋值给快照锁定下的当前工位上下文,实现数据完全隔离
|
||||
_scopedContext.Program.Parameters = program.Parameters;
|
||||
_scopedContext.Program.StepCollection = program.StepCollection;
|
||||
_scopedContext.Program.ErrorStepCollection = program.ErrorStepCollection;
|
||||
_scopedContext.CurrentFilePath = filePath;
|
||||
foreach (var item in _systemConfig.SharedParameterList)
|
||||
{
|
||||
var parameter = _scopedContext?.Program?.Parameters?.FirstOrDefault(x => x.Name == item.ParameterName);
|
||||
|
||||
if (parameter != null)
|
||||
{
|
||||
parameter.Value = item.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
67
MainModule/ViewModels/MainViewModel.cs
Normal file
67
MainModule/ViewModels/MainViewModel.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using UIShare.PubEvent;
|
||||
using MainModule.ViewModels;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
|
||||
namespace MainModule.ViewModels
|
||||
{
|
||||
public class MainViewModel : NavigateViewModelBase, IRegionMemberLifetime
|
||||
{
|
||||
#region 私有字段
|
||||
private bool IsInitialized = false;
|
||||
private string _expandedCellName = string.Empty;
|
||||
#endregion
|
||||
|
||||
#region 属性
|
||||
public bool KeepAlive => true; // 保持存活
|
||||
|
||||
public string ExpandedCellName
|
||||
{
|
||||
get => _expandedCellName;
|
||||
set => SetProperty(ref _expandedCellName, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
public ICommand LoadedCommand { get; set; }
|
||||
#endregion
|
||||
|
||||
public MainViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
LoadedCommand = new DelegateCommand(OnLoaded);
|
||||
_eventAggregator.GetEvent<ExpandViewEvent>().Subscribe(OnCellExpandRequested);
|
||||
}
|
||||
|
||||
#region 命令处理与事件
|
||||
private void OnLoaded()
|
||||
{
|
||||
if (IsInitialized) return;
|
||||
for (int i = 1; i <= 9; i++)
|
||||
{
|
||||
var parameters = new NavigationParameters { { "Name", $"TestCell{i}" } };
|
||||
_regionManager.RequestNavigate($"TestCell{i}", "ProtocolStartView", parameters);
|
||||
}
|
||||
IsInitialized = true;
|
||||
}
|
||||
|
||||
// 不再物理搬迁视图,只需修改一个字符串属性 ExpandedCellName,
|
||||
// XAML 里每个单元的 Style.Triggers 会根据该值处理隐藏 / 跨越 3x3。
|
||||
private void OnCellExpandRequested(string cellName)
|
||||
{
|
||||
ExpandedCellName = cellName ?? string.Empty;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 重写
|
||||
public override bool IsNavigationTarget(NavigationContext navigationContext)
|
||||
{
|
||||
// 之前帮你改好的单例复用逻辑(防止重复 new 和初始化视图)
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
107
MainModule/ViewModels/ProtocolStartViewModel.cs
Normal file
107
MainModule/ViewModels/ProtocolStartViewModel.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using Prism.Navigation.Regions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace MainModule.ViewModels
|
||||
{
|
||||
public class ProtocolStartViewModel : NavigateViewModelBase
|
||||
{
|
||||
#region 私有字段
|
||||
private string _testStatus;
|
||||
private string _moduleColor;
|
||||
private GlobalInfo _globalInfo;
|
||||
#endregion
|
||||
|
||||
#region 属性
|
||||
public string TestStatus
|
||||
{
|
||||
get => _testStatus;
|
||||
set => SetProperty(ref _testStatus, value);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
public DelegateCommand StartProtocolCommand { get; }
|
||||
#endregion
|
||||
|
||||
public ProtocolStartViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
_globalInfo = containerProvider.Resolve<GlobalInfo>();
|
||||
StartProtocolCommand = new DelegateCommand(OnStart);
|
||||
}
|
||||
|
||||
#region 命令处理与事件
|
||||
private void OnStart()
|
||||
{
|
||||
// 只在当前格子所属的 Cell Region 内完成切换,不会影响其他格子位置
|
||||
SwitchNavigate("AutomatedTestingView");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定位当前视图所在的 Cell Region(TestCell1..TestCell9),
|
||||
/// 在同一个 Region 内完成跳转。Region 位置由 XAML Grid 锁定,永远不会错位。
|
||||
/// </summary>
|
||||
public void SwitchNavigate(string viewName)
|
||||
{
|
||||
// 1. 反向查找:哪个 Cell Region 当前托着“我”这个 ProtocolStartView
|
||||
for (int i = 1; i <= 9; i++)
|
||||
{
|
||||
var regionName = $"TestCell{i}";
|
||||
if (!_regionManager.Regions.ContainsRegionWithName(regionName)) continue;
|
||||
|
||||
var region = _regionManager.Regions[regionName];
|
||||
var myView = region.Views
|
||||
.OfType<FrameworkElement>()
|
||||
.FirstOrDefault(v => v.DataContext == this);
|
||||
|
||||
if (myView == null) continue;
|
||||
|
||||
// 2. 透传名称与颜色参数,使 AutomatedTestingViewModel 能正确初始化
|
||||
var parameters = new NavigationParameters();
|
||||
parameters.Add("Name", TestStatus);
|
||||
_globalInfo.CurrentOpeningScope = TestStatus;
|
||||
// 3. 在本格子 Region 内请求导航,导航成功后再移除旧的 ProtocolStartView 以释放资源
|
||||
_regionManager.RequestNavigate(regionName, viewName, navResult =>
|
||||
{
|
||||
if (navResult.Success == true)
|
||||
{
|
||||
region.Remove(myView);
|
||||
}
|
||||
}, parameters);
|
||||
_moduleManager.LoadModule("MonitorModule");
|
||||
_moduleManager.LoadModule("SettingModule");
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 重写
|
||||
public override bool IsNavigationTarget(NavigationContext navigationContext)
|
||||
{
|
||||
if (navigationContext.Parameters.ContainsKey("Name"))
|
||||
return TestStatus == navigationContext.Parameters.GetValue<string>("Name");
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||
{
|
||||
base.OnNavigatedTo(navigationContext);
|
||||
|
||||
// 接收导航传参:名称与颜色
|
||||
if (navigationContext.Parameters.ContainsKey("Name"))
|
||||
TestStatus = navigationContext.Parameters.GetValue<string>("Name");
|
||||
|
||||
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user