920 lines
38 KiB
C#
920 lines
38 KiB
C#
|
|
using DeviceCommand.Devices;
|
|
using Logger;
|
|
using MaterialDesignThemes.Wpf;
|
|
using Microsoft.Win32;
|
|
using Model.Models;
|
|
using Notifications.Wpf.Core;
|
|
using System.Collections.Concurrent;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using System.Windows.Threading;
|
|
using UIShare;
|
|
using UIShare.GlobalVariable;
|
|
using UIShare.PubEvent;
|
|
using UIShare.UIViewModel;
|
|
using SqlSugar;
|
|
|
|
namespace ADP.ViewModels
|
|
{
|
|
public class ShellViewModel : BindableBase
|
|
{
|
|
#region 私有字段
|
|
private string _Title = "";
|
|
private bool _IsLeftDrawerOpen;
|
|
|
|
private readonly ConcurrentDictionary<string, Task> _executionTasks = new();
|
|
private readonly ConcurrentDictionary<string, Task> _errorExecutionTasks = new();
|
|
|
|
private readonly IEventAggregator _eventAggregator;
|
|
private readonly IRegionManager _regionManager;
|
|
private readonly IDialogService _dialogService;
|
|
private readonly IContainerProvider _containerProvider;
|
|
private readonly INotificationManager _notificationManager;
|
|
private readonly IModuleManager _moduleManager;
|
|
private readonly GlobalInfo _globalInfo;
|
|
|
|
// 💡 新增:UI 刷新定时器,用于高频让前端重新拉取当前工位的 SW 累计时间
|
|
private readonly DispatcherTimer _uiRefreshTimer;
|
|
#endregion
|
|
|
|
#region 属性
|
|
public string Title
|
|
{
|
|
get => _Title;
|
|
set => SetProperty(ref _Title, value);
|
|
}
|
|
public bool IsLeftDrawerOpen
|
|
{
|
|
get => _IsLeftDrawerOpen;
|
|
set => SetProperty(ref _IsLeftDrawerOpen, value);
|
|
}
|
|
|
|
public TimeSpan RunningTime
|
|
{
|
|
get
|
|
{
|
|
if (CurrentContext == null) return TimeSpan.Zero;
|
|
return CurrentContext.SW.IsRunning ? CurrentContext.SW.Elapsed : CurrentContext.RunningTime;
|
|
}
|
|
set
|
|
{
|
|
if (CurrentContext != null && CurrentContext.RunningTime != value)
|
|
{
|
|
CurrentContext.RunningTime = value;
|
|
RaisePropertyChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsTerminate
|
|
{
|
|
get => CurrentContext?.IsTerminate ?? false;
|
|
set
|
|
{
|
|
if (CurrentContext != null && CurrentContext.IsTerminate != value)
|
|
{
|
|
CurrentContext.IsTerminate = value;
|
|
RaisePropertyChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
private ScopedContext? CurrentContext =>
|
|
_globalInfo.ContextDic.TryGetValue(_globalInfo.CurrentScope, out var ctx) ? ctx : null;
|
|
private StepRunning? CurrentRunner =>
|
|
_globalInfo.StepRunningDic.TryGetValue(_globalInfo.CurrentScope, out var runner) ? runner : null;
|
|
private SystemConfig? CurrentConfig =>
|
|
_globalInfo.ConfigDic.TryGetValue(_globalInfo.CurrentScope, out var config) ? config : null;
|
|
public string RunState
|
|
{
|
|
get => CurrentContext?.RunState ?? "运行";
|
|
set
|
|
{
|
|
if (CurrentContext != null && CurrentContext.RunState != value)
|
|
{
|
|
CurrentContext.RunState = value;
|
|
RaisePropertyChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool SingleStep
|
|
{
|
|
get => CurrentContext?.SingleStep ?? false;
|
|
set
|
|
{
|
|
if (CurrentContext != null && CurrentContext.SingleStep != value)
|
|
{
|
|
CurrentContext.SingleStep = value;
|
|
RaisePropertyChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public PackIconKind RunIcon
|
|
{
|
|
get => CurrentContext?.RunIcon ?? PackIconKind.Play;
|
|
set
|
|
{
|
|
if (CurrentContext != null && CurrentContext.RunIcon != value)
|
|
{
|
|
CurrentContext.RunIcon = value;
|
|
RaisePropertyChanged();
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region 命令
|
|
public ICommand LeftDrawerOpenCommand { get; set; }
|
|
public ICommand MinimizeCommand { get; set; }
|
|
public ICommand MaximizeCommand { get; set; }
|
|
public ICommand CloseCommand { get; set; }
|
|
public ICommand NavigateCommand { get; set; }
|
|
public ICommand LoadCommand { get; set; }
|
|
public ICommand RefreshCommand { get; set; }
|
|
public ICommand DestroyCommand { get; set; }
|
|
public ICommand RunningCommand { get; set; }
|
|
public ICommand RunSingleCommand { get; set; }
|
|
public ICommand RestorationCommand { get; set; }
|
|
public ICommand RunAbnormalStepsCommand { get; set; }
|
|
public ICommand SaveAsCommand { get; set; }
|
|
public ICommand SaveCommand { get; set; }
|
|
public ICommand OpenCommand { get; set; }
|
|
public ICommand NewCommand { get; set; }
|
|
public ICommand SetDefaultCommand { get; set; }
|
|
public ICommand ShowDialogManagerViewCommand { get; set; }
|
|
public ICommand SelectCANSignalMonitorCommand { get; set; }
|
|
public ICommand SelectCANMessageCommand { get; set; }
|
|
public ICommand MonitorValueSettingCommand { get; set; }
|
|
public ICommand GetFileStringCommand { get; set; }
|
|
public ICommand SilenceBuzzerCommand { get; set; }
|
|
public ICommand GoBackCommand { get; set; }
|
|
#endregion
|
|
|
|
#region 子程序导航
|
|
|
|
public bool CanGoBack =>
|
|
(CurrentContext?.MainNav.CanGoBack ?? false) ||
|
|
(CurrentContext?.ErrorNav.CanGoBack ?? false);
|
|
|
|
private void OnGoBack()
|
|
{
|
|
var ctx = CurrentContext;
|
|
if (ctx == null) return;
|
|
// 工具栏按钮同时重置两个 Tab 的导航
|
|
ctx.MainNav.Reset(ctx.Program, "主程序");
|
|
ctx.ErrorNav.Reset(ctx.Program, "错误程序");
|
|
}
|
|
|
|
#endregion
|
|
|
|
public ShellViewModel(IContainerProvider containerProvider)
|
|
{
|
|
_containerProvider = containerProvider;
|
|
_dialogService= containerProvider.Resolve<IDialogService>();
|
|
_globalInfo = containerProvider.Resolve<GlobalInfo>();
|
|
_eventAggregator = containerProvider.Resolve<IEventAggregator>();
|
|
_regionManager = containerProvider.Resolve<IRegionManager>();
|
|
_notificationManager = containerProvider.Resolve<INotificationManager>();
|
|
_moduleManager = containerProvider.Resolve<IModuleManager>();
|
|
LeftDrawerOpenCommand = new DelegateCommand(LeftDrawerOpen);
|
|
ShowDialogManagerViewCommand = new DelegateCommand(ShowDialogManagerView);
|
|
MinimizeCommand = new DelegateCommand<Window>(MinimizeWindow);
|
|
MaximizeCommand = new DelegateCommand<Window>(MaximizeWindow);
|
|
CloseCommand = new DelegateCommand<Window>(CloseWindow);
|
|
NavigateCommand = new DelegateCommand<string>(Navigate);
|
|
LoadCommand = new DelegateCommand(Load);
|
|
RefreshCommand = new DelegateCommand(OnRefresh);
|
|
DestroyCommand = new DelegateCommand(OnDestroy);
|
|
RunningCommand = new DelegateCommand(OnRunning);
|
|
RunSingleCommand = new DelegateCommand(RunSingle);
|
|
RestorationCommand = new AsyncDelegateCommand(OnRestoration);
|
|
RunAbnormalStepsCommand = new AsyncDelegateCommand(OnRunAbnormalSteps);
|
|
NewCommand = new DelegateCommand(New);
|
|
OpenCommand = new AsyncDelegateCommand<string>(Open);
|
|
SaveAsCommand = new DelegateCommand(SaveAs);
|
|
SaveCommand = new DelegateCommand(Save);
|
|
SetDefaultCommand = new DelegateCommand(SetDefault);
|
|
SelectCANSignalMonitorCommand = new DelegateCommand(SelectCANSignalMonitor);
|
|
SelectCANMessageCommand = new DelegateCommand(SelectCANMessage);
|
|
MonitorValueSettingCommand = new DelegateCommand(MonitorValueSetting);
|
|
GetFileStringCommand = new DelegateCommand(GetFileString);
|
|
SilenceBuzzerCommand = new AsyncDelegateCommand(SilenceBuzzer);
|
|
GoBackCommand = new DelegateCommand(OnGoBack);
|
|
|
|
_globalInfo.ContextDic.TryAdd("default", new ScopedContext());
|
|
|
|
_eventAggregator.GetEvent<LoginSuccessEvent>().Subscribe(() =>
|
|
{
|
|
Application.Current.MainWindow.Show();
|
|
_regionManager.RequestNavigate("ShellViewManager", "MainView");
|
|
});
|
|
|
|
_eventAggregator.GetEvent<RunSingalCompletedEvent>().Subscribe(UpdateRunIcon);
|
|
|
|
_globalInfo.ScopeChanged += (s, e) =>
|
|
{
|
|
RefreshAllContextProperties();
|
|
};
|
|
|
|
// 💡 初始化轻量级 UI 定时器:每 100 毫秒刷新一次当前可见工位的界面时间
|
|
_uiRefreshTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
|
|
_uiRefreshTimer.Tick += (s, e) => RaisePropertyChanged(nameof(RunningTime));
|
|
_uiRefreshTimer.Start();
|
|
}
|
|
|
|
|
|
private void RefreshAllContextProperties()
|
|
{
|
|
RaisePropertyChanged(nameof(RunState));
|
|
RaisePropertyChanged(nameof(SingleStep));
|
|
RaisePropertyChanged(nameof(RunIcon));
|
|
RaisePropertyChanged(nameof(RunningTime));
|
|
RaisePropertyChanged(nameof(IsTerminate));
|
|
}
|
|
|
|
#region 命令处理与事件
|
|
|
|
private async Task SilenceBuzzer()
|
|
{
|
|
_eventAggregator.GetEvent<SilenceBuzzerEvent>().Publish(_globalInfo.CurrentScope);
|
|
|
|
}
|
|
private void GetFileString()
|
|
{
|
|
var openFileDialog = new OpenFileDialog
|
|
{
|
|
Title = "选择文件",
|
|
Filter = "所有文件 (*.*)|*.*",
|
|
};
|
|
if (openFileDialog.ShowDialog() == true)
|
|
{
|
|
string filePath = openFileDialog.FileName;
|
|
Application.Current.Dispatcher.Invoke(() =>
|
|
{
|
|
Clipboard.SetDataObject(filePath);
|
|
});
|
|
}
|
|
}
|
|
private void MonitorValueSetting()
|
|
{
|
|
if ( _globalInfo.CurrentScope == "default")
|
|
{
|
|
LoggerHelper.Warn("当前作用域未配置 CAN 设备,无法打开报文选择窗口。");
|
|
return;
|
|
}
|
|
_dialogService.ShowDialog("ValueLimitView", new DialogParameters
|
|
{
|
|
{ "Scope", _globalInfo.ScopeDic[_globalInfo.CurrentScope] }
|
|
}, _ => { });
|
|
}
|
|
|
|
private void SelectCANSignalMonitor()
|
|
{
|
|
var canfd = CurrentConfig?.CANFD;
|
|
if (canfd == null || _globalInfo.CurrentScope == "default")
|
|
{
|
|
return;
|
|
}
|
|
|
|
_dialogService.ShowDialog("CollectCANSignalSettingView", new DialogParameters
|
|
{
|
|
{ "SystemConfig", CurrentConfig }
|
|
}, _ => { });
|
|
}
|
|
|
|
private void SelectCANMessage()
|
|
{
|
|
var canfd = CurrentConfig?.CANFD;
|
|
if (canfd == null||_globalInfo.CurrentScope=="default")
|
|
{
|
|
LoggerHelper.Warn("当前作用域未配置 CAN 设备,无法打开报文选择窗口。");
|
|
return;
|
|
}
|
|
|
|
_dialogService.ShowDialog("SelectCanMessageView", new DialogParameters
|
|
{
|
|
{ "CANFD", canfd }
|
|
}, _ => { });
|
|
}
|
|
|
|
private void ShowDialogManagerView()
|
|
{
|
|
_eventAggregator.GetEvent<CancelMinimizeEvent>().Publish();
|
|
}
|
|
private async void OnRunning()
|
|
{
|
|
string runningScope = _globalInfo.CurrentScope;
|
|
ScopedContext? targetContext = CurrentContext;
|
|
StepRunning? targetRunner = CurrentRunner;
|
|
|
|
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null || targetRunner == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (targetContext.RunState == "运行")
|
|
{
|
|
targetContext.SingleStep = false;
|
|
LoggerHelper.InfoWithNotify(runningScope, $"{_globalInfo.UserName} 执行工位 [{runningScope}] 运行命令");
|
|
|
|
targetContext.RunState = "暂停";
|
|
targetContext.RunIcon = PackIconKind.Pause;
|
|
|
|
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
|
|
|
if (targetContext.IsStop == null)
|
|
{
|
|
targetContext.IsStop = false;
|
|
|
|
// 💡 启动或恢复:启动属于该快照工位独立的 SW 计时器
|
|
targetContext.SW.Start();
|
|
|
|
if (!_executionTasks.TryGetValue(runningScope, out var existingTask) || existingTask == null)
|
|
{
|
|
existingTask = targetRunner.ExecuteSteps(targetContext.Program, cancellationToken: targetRunner.stepCTS.Token);
|
|
_executionTasks[runningScope] = existingTask;
|
|
}
|
|
|
|
try
|
|
{
|
|
await existingTask;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LoggerHelper.ErrorWithNotify(runningScope, $"工位 [{runningScope}] 异常中止: {ex.Message}");
|
|
}
|
|
|
|
// 💡 测试正常完毕或取消:停止当前工位的 SW,并将最终时间固化到 RunningTime 字段中
|
|
targetContext.SW.Stop();
|
|
targetContext.RunningTime = targetContext.SW.Elapsed;
|
|
|
|
targetContext.RunState = "运行";
|
|
targetContext.RunIcon = PackIconKind.Play;
|
|
targetContext.IsStop = null;
|
|
|
|
if (targetRunner.stepCTS.IsCancellationRequested)
|
|
{
|
|
targetRunner.stepCTS = new CancellationTokenSource();
|
|
_executionTasks.TryRemove(runningScope, out _);
|
|
}
|
|
else if (_executionTasks.TryGetValue(runningScope, out var currentTask) && currentTask != null && currentTask.IsCompleted)
|
|
{
|
|
targetContext.IsTerminate = true;
|
|
_executionTasks.TryRemove(runningScope, out _);
|
|
}
|
|
|
|
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
|
}
|
|
else if (targetContext.IsStop == true)
|
|
{
|
|
targetContext.IsStop = false;
|
|
// 💡 从暂停中恢复,继续累加当前工位计时
|
|
targetContext.SW.Start();
|
|
}
|
|
}
|
|
else // 用户点击了暂停
|
|
{
|
|
LoggerHelper.InfoWithNotify(runningScope, $"{_globalInfo.UserName} 点击工位 [{runningScope}] 暂停命令");
|
|
targetContext.SingleStep = true;
|
|
targetContext.IsStop = true;
|
|
targetContext.RunState = "运行";
|
|
targetContext.RunIcon = PackIconKind.Play;
|
|
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
|
}
|
|
}
|
|
|
|
private async void RunSingle()
|
|
{
|
|
string runningScope = _globalInfo.CurrentScope;
|
|
ScopedContext? targetContext = CurrentContext;
|
|
StepRunning? targetRunner = CurrentRunner;
|
|
|
|
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null || targetRunner == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (targetContext.RunState == "运行")
|
|
{
|
|
targetContext.SingleStep = true;
|
|
LoggerHelper.InfoWithNotify(runningScope, $"{_globalInfo.UserName} 执行工位 [{runningScope}] 单步执行命令");
|
|
|
|
targetContext.RunState = "暂停";
|
|
targetContext.RunIcon = PackIconKind.Pause;
|
|
|
|
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
|
|
|
if (targetContext.IsStop == null)
|
|
{
|
|
targetContext.IsStop = false;
|
|
|
|
// 💡 单步启动:让专属工位的 SW 跑起来
|
|
targetContext.SW.Start();
|
|
|
|
if (!_executionTasks.TryGetValue(runningScope, out var existingTask) || existingTask == null)
|
|
{
|
|
existingTask = targetRunner.ExecuteSteps(targetContext.Program, cancellationToken: targetRunner.stepCTS.Token);
|
|
_executionTasks[runningScope] = existingTask;
|
|
}
|
|
|
|
try
|
|
{
|
|
await existingTask;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LoggerHelper.ErrorWithNotify(runningScope, $"工位 [{runningScope}] 单步执行异常: {ex.Message}");
|
|
}
|
|
|
|
// 💡 单步单步完成后会被 StepRunning 挂起,在此暂时停止 SW
|
|
targetContext.SW.Stop();
|
|
targetContext.RunningTime = targetContext.SW.Elapsed;
|
|
|
|
targetContext.RunState = "运行";
|
|
targetContext.RunIcon = PackIconKind.Play;
|
|
targetContext.IsStop = null;
|
|
|
|
if (targetRunner.stepCTS.IsCancellationRequested)
|
|
{
|
|
targetRunner.stepCTS = new CancellationTokenSource();
|
|
_executionTasks.TryRemove(runningScope, out _);
|
|
}
|
|
else if (_executionTasks.TryGetValue(runningScope, out var currentTask) && currentTask != null && currentTask.IsCompleted)
|
|
{
|
|
targetContext.IsTerminate = true;
|
|
_executionTasks.TryRemove(runningScope, out _);
|
|
}
|
|
|
|
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
|
}
|
|
else if (targetContext.IsStop == true)
|
|
{
|
|
targetContext.IsStop = false;
|
|
// 💡 继续单步,继续计时
|
|
targetContext.SW.Start();
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task OnRestoration()
|
|
{
|
|
string runningScope = _globalInfo.CurrentScope;
|
|
ScopedContext? targetContext = CurrentContext;
|
|
StepRunning? targetRunner = CurrentRunner;
|
|
|
|
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null || targetRunner == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
LoggerHelper.InfoWithNotify(runningScope, $"{_globalInfo.UserName} 执行工位 [{runningScope}] 复位命令");
|
|
|
|
// 重置导航栈,回到根程序界面
|
|
targetContext.ResetNavigation();
|
|
|
|
_executionTasks.TryGetValue(runningScope, out var currentTask);
|
|
_errorExecutionTasks.TryGetValue(runningScope, out var errorTask);
|
|
|
|
if (currentTask != null || errorTask != null)
|
|
{
|
|
targetRunner.stepCTS.Cancel();
|
|
targetRunner.errorStepCTS.Cancel();
|
|
|
|
// 💡 等待旧任务真正结束,而不是只等 200ms
|
|
if (currentTask != null)
|
|
{
|
|
try { await currentTask; } catch { /* 取消导致的异常忽略 */ }
|
|
}
|
|
if (errorTask != null)
|
|
{
|
|
try { await errorTask; } catch { /* 取消导致的异常忽略 */ }
|
|
}
|
|
|
|
// 💡 彻底复位:替换 CTS、清理任务字典、停止计时器、归零状态
|
|
targetRunner.stepCTS = new CancellationTokenSource();
|
|
targetRunner.errorStepCTS = new CancellationTokenSource();
|
|
_executionTasks.TryRemove(runningScope, out _);
|
|
_errorExecutionTasks.TryRemove(runningScope, out _);
|
|
|
|
targetContext.SW.Reset();
|
|
targetContext.RunningTime = TimeSpan.Zero;
|
|
|
|
targetContext.IsStop = null;
|
|
targetRunner.ResetAllStepStatus(targetContext.Program.StepCollection);
|
|
targetRunner.ResetAllStepStatus(targetContext.Program.ErrorStepCollection);
|
|
targetContext.IsTerminate = false;
|
|
}
|
|
else
|
|
{
|
|
targetRunner.stepCTS = new CancellationTokenSource();
|
|
targetRunner.errorStepCTS = new CancellationTokenSource();
|
|
|
|
// 💡 无任务状态下的直接归零
|
|
targetContext.SW.Reset();
|
|
targetContext.RunningTime = TimeSpan.Zero;
|
|
|
|
targetContext.IsStop = null;
|
|
targetRunner.ResetAllStepStatus(targetContext.Program.StepCollection);
|
|
targetRunner.ResetAllStepStatus(targetContext.Program.ErrorStepCollection);
|
|
targetContext.IsTerminate = false;
|
|
}
|
|
|
|
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
|
}
|
|
|
|
private async Task OnRunAbnormalSteps()
|
|
{
|
|
string runningScope = _globalInfo.CurrentScope;
|
|
ScopedContext? targetContext = CurrentContext;
|
|
StepRunning? targetRunner = CurrentRunner;
|
|
|
|
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null || targetRunner == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
LoggerHelper.InfoWithNotify(runningScope, $"{_globalInfo.UserName} 执行工位 [{runningScope}] 异常流程命令");
|
|
|
|
// 💡 异常测试启动计时
|
|
targetContext.SW.Start();
|
|
|
|
if (!_errorExecutionTasks.TryGetValue(runningScope, out var existingErrorTask) || existingErrorTask == null)
|
|
{
|
|
existingErrorTask = targetRunner.ExecuteErrorSteps(targetContext.Program, cancellationToken: targetRunner.errorStepCTS.Token);
|
|
_errorExecutionTasks[runningScope] = existingErrorTask;
|
|
}
|
|
|
|
try
|
|
{
|
|
await existingErrorTask;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LoggerHelper.ErrorWithNotify(runningScope, $"工位 [{runningScope}] 异常流程执行出错: {ex.Message}");
|
|
}
|
|
|
|
// 💡 异常测试结束停止计时
|
|
targetContext.SW.Stop();
|
|
targetContext.RunningTime = targetContext.SW.Elapsed;
|
|
|
|
if (targetRunner.errorStepCTS.IsCancellationRequested)
|
|
{
|
|
targetRunner.errorStepCTS = new CancellationTokenSource();
|
|
_errorExecutionTasks.TryRemove(runningScope, out _);
|
|
}
|
|
else if (_errorExecutionTasks.TryGetValue(runningScope, out var currentErrorTask) && currentErrorTask != null && currentErrorTask.IsCompleted)
|
|
{
|
|
targetContext.IsTerminate = true;
|
|
_errorExecutionTasks.TryRemove(runningScope, out _);
|
|
}
|
|
|
|
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
|
}
|
|
|
|
private void SetDefault()
|
|
{
|
|
if (CurrentConfig == null) return;
|
|
// 💡 1. 抓取触发瞬间的 Scope 与 Context 快照
|
|
string runningScope = _globalInfo.CurrentScope;
|
|
ScopedContext? targetContext = CurrentContext;
|
|
|
|
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
|
|
|
|
if (targetContext.CurrentFilePath != null)
|
|
{
|
|
CurrentConfig.DefaultProgramFilePath = targetContext.CurrentFilePath;
|
|
ConfigService.Save(CurrentConfig);
|
|
LoggerHelper.SuccessWithNotify(runningScope, $"工位 [{runningScope}] 已成功将当前程序设为默认启动程序");
|
|
}
|
|
}
|
|
|
|
private void New()
|
|
{
|
|
// 💡 1. 抓取快照
|
|
string runningScope = _globalInfo.CurrentScope;
|
|
ScopedContext? targetContext = CurrentContext;
|
|
|
|
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
|
|
CurrentConfig.CurrentADPFile = "新建ADP文件";
|
|
// 💡 2. 严格对快照隔离的 Context 数据进行清理,不影响其他工位
|
|
targetContext.CurrentFilePath = null;
|
|
targetContext.Program.Parameters.Clear();
|
|
targetContext.Program.StepCollection.Clear();
|
|
targetContext.Program.ErrorStepCollection.Clear();
|
|
targetContext.ResetNavigation();
|
|
foreach (var item in CurrentConfig.ParameterList)
|
|
{
|
|
var param = CurrentConfig.SharedParameterList.FirstOrDefault(x => x.ParameterName == item.Name);
|
|
if (param != null)
|
|
{
|
|
item.Value = param.Value;
|
|
}
|
|
targetContext.Program.Parameters.Add(item);
|
|
}
|
|
LoggerHelper.InfoWithNotify(runningScope, $"工位 [{runningScope}] 创建了空程序文件");
|
|
|
|
// 如果当前正看着该工位,刷新 UI 显示
|
|
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
|
}
|
|
|
|
private async Task Open(string filePath = null)
|
|
{
|
|
// 💡 1. 抓取进方法瞬间的快照,锁死上下文
|
|
string runningScope = _globalInfo.CurrentScope;
|
|
ScopedContext? targetContext = CurrentContext;
|
|
|
|
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
|
|
|
|
try
|
|
{
|
|
// 如果没有传路径,弹出文件选择对话框(WPF 对话框是模态的,会阻塞当前 UI 线程)
|
|
if (string.IsNullOrEmpty(filePath))
|
|
{
|
|
var openFileDialog = new Microsoft.Win32.OpenFileDialog
|
|
{
|
|
Filter = "ADP程序文件|*.ADP|所有文件|*.*",
|
|
Title = $"工位 [{runningScope}] 打开程序",
|
|
};
|
|
|
|
if (openFileDialog.ShowDialog() != true)
|
|
return; // 用户取消选择
|
|
|
|
filePath = openFileDialog.FileName;
|
|
}
|
|
|
|
// 确认文件存在
|
|
if (!File.Exists(filePath))
|
|
{
|
|
LoggerHelper.ErrorWithNotify(runningScope, $"文件不存在: {filePath}");
|
|
return;
|
|
}
|
|
|
|
// 读取 JSON 文件
|
|
string json = File.ReadAllText(filePath);
|
|
|
|
// 反序列化为 ProgramVM
|
|
var program = Newtonsoft.Json.JsonConvert.DeserializeObject<ProgramVM>(json);
|
|
|
|
if (program == null)
|
|
{
|
|
LoggerHelper.WarnWithNotify(runningScope, $"文件格式不正确或为空: {filePath}");
|
|
return;
|
|
}
|
|
CurrentConfig.CurrentADPFile = filePath;
|
|
// 💡 2. 严格赋值给快照锁定下的当前工位上下文,实现数据完全隔离
|
|
targetContext.Program.Parameters = program.Parameters;
|
|
targetContext.Program.StepCollection = program.StepCollection;
|
|
targetContext.Program.ErrorStepCollection = program.ErrorStepCollection;
|
|
targetContext.CurrentFilePath = filePath;
|
|
|
|
foreach (var item in CurrentConfig.SharedParameterList)
|
|
{
|
|
var parameter = targetContext?.Program?.Parameters?.FirstOrDefault(x => x.Name == item.ParameterName);
|
|
|
|
if (parameter != null)
|
|
{
|
|
parameter.Value = item.Value;
|
|
}
|
|
}
|
|
|
|
LoggerHelper.SuccessWithNotify(runningScope, $"工位 [{runningScope}] 成功打开文件: {filePath}");
|
|
|
|
// 💡 3. 安全调用异步复位,确保重置的是对应工位的数据
|
|
// 注意:由于在 OnRestoration 内部第一行也做了快照拦截,
|
|
// 如果用户此时正好在看这个工位,它会自动完美执行
|
|
await OnRestoration();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LoggerHelper.ErrorWithNotify(runningScope, $"工位 [{runningScope}] 打开文件失败: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
// 💡 4. 异步回归:不管中途用户切去了哪里,回到该工位时及时刷新显示
|
|
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
|
}
|
|
}
|
|
|
|
private void SaveAs()
|
|
{
|
|
if (!_globalInfo.IsAdmin) return;
|
|
|
|
// 💡 1. 抓取快照
|
|
string runningScope = _globalInfo.CurrentScope;
|
|
ScopedContext? targetContext = CurrentContext;
|
|
|
|
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
|
|
|
|
string defaultPath = @"D:\ADP\子程序";
|
|
if (!Directory.Exists(defaultPath))
|
|
Directory.CreateDirectory(defaultPath);
|
|
|
|
var saveFileDialog = new Microsoft.Win32.SaveFileDialog
|
|
{
|
|
Filter = "ADP程序文件|*.adp|所有文件|*.*",
|
|
Title = $"工位 [{runningScope}] 程序另存为",
|
|
FileName = "NewProgram.ADP",
|
|
InitialDirectory = defaultPath
|
|
};
|
|
|
|
if (saveFileDialog.ShowDialog() == true)
|
|
{
|
|
// 💡 2. 将新路径安全写入快照工位
|
|
CurrentConfig.CurrentADPFile = saveFileDialog.FileName;
|
|
targetContext.CurrentFilePath = saveFileDialog.FileName;
|
|
|
|
// 💡 3. 调用你的底层文件保存逻辑,传入快照工位的 Program 模型
|
|
SaveProgramToFile(targetContext.CurrentFilePath, targetContext.Program);
|
|
|
|
LoggerHelper.InfoWithNotify(runningScope, $"{_globalInfo.UserName} 另存为文件成功: {saveFileDialog.FileName}");
|
|
|
|
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
|
}
|
|
}
|
|
|
|
private void Save()
|
|
{
|
|
if (!_globalInfo.IsAdmin) return;
|
|
|
|
// 💡 1. 抓取快照
|
|
string runningScope = _globalInfo.CurrentScope;
|
|
ScopedContext? targetContext = CurrentContext;
|
|
|
|
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
|
|
|
|
// 💡 2. 判断当前工位是否有历史路径
|
|
if (targetContext.CurrentFilePath == null)
|
|
{
|
|
SaveAs();
|
|
return;
|
|
}
|
|
|
|
// 💡 3. 持久化当前快照工位的 Program
|
|
SaveProgramToFile(targetContext.CurrentFilePath, targetContext.Program);
|
|
LoggerHelper.SuccessWithNotify(_globalInfo.CurrentScope,$"工位 [{runningScope}] 程序保存成功!");
|
|
}
|
|
|
|
// 💡 辅助方法:建议将你的通用序列化落盘代码调整为接收 ProgramVM 参数,提高复用性
|
|
private void SaveProgramToFile(string filePath, ProgramVM ProgramVM)
|
|
{
|
|
try
|
|
{
|
|
string json = Newtonsoft.Json.JsonConvert.SerializeObject(ProgramVM, Newtonsoft.Json.Formatting.Indented);
|
|
File.WriteAllText(filePath, json);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope,$"写入程序文件失败: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void UpdateRunIcon(string obj)
|
|
{
|
|
CurrentContext?.SW.Stop();
|
|
CurrentContext.RunningTime = CurrentContext.SW.Elapsed;
|
|
RunIcon = obj switch
|
|
{
|
|
"Play" => PackIconKind.Play,
|
|
"Pause" => PackIconKind.Pause,
|
|
_ => RunIcon
|
|
};
|
|
}
|
|
|
|
private void OnRefresh()
|
|
{
|
|
_eventAggregator.GetEvent<ExpandViewEvent>().Publish("");
|
|
_globalInfo.CurrentScope = "default";
|
|
}
|
|
private void OnDestroy()
|
|
{
|
|
if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope)) return;
|
|
|
|
string targetRegionName = _globalInfo.CurrentScope;
|
|
|
|
// 💡 物理销毁工位时,顺便清理任务字典,防止内存泄漏
|
|
_executionTasks.TryRemove(targetRegionName, out _);
|
|
_errorExecutionTasks.TryRemove(targetRegionName, out _);
|
|
|
|
if (_regionManager.Regions.ContainsRegionWithName(targetRegionName))
|
|
{
|
|
var region = _regionManager.Regions[targetRegionName];
|
|
var viewsToDestroy = region.Views.ToList();
|
|
|
|
foreach (var view in viewsToDestroy)
|
|
{
|
|
if (view is FrameworkElement element)
|
|
{
|
|
var viewModel = element.DataContext;
|
|
region.Remove(view);
|
|
if (viewModel is IDisposable disposableVM) disposableVM.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
var parameters = new NavigationParameters { { "Name", targetRegionName } };
|
|
_regionManager.RequestNavigate(targetRegionName, "ProtocolStartView", parameters);
|
|
_eventAggregator.GetEvent<ExpandViewEvent>().Publish("");
|
|
}
|
|
private void Load()
|
|
{
|
|
_notificationManager.ShowAsync(new NotificationContent { Title = "登录成功", Message = "", Type = NotificationType.Success });
|
|
//默认导航到主界面
|
|
Type moduleAType = typeof(MainModule.MainModule);
|
|
_moduleManager.LoadModule(moduleAType.Name);
|
|
}
|
|
private void Navigate(string content)
|
|
{
|
|
switch (content)
|
|
{
|
|
case "主界面":
|
|
if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope))
|
|
{
|
|
_regionManager.RequestNavigate("ShellViewManager", "MainView");
|
|
break;
|
|
}
|
|
var TestingRegion = _globalInfo.CurrentScope;
|
|
if (!_regionManager.Regions.ContainsRegionWithName(TestingRegion)) break;
|
|
_regionManager.RequestNavigate(TestingRegion, "AutomatedTestingView");
|
|
break;
|
|
case "监控界面":
|
|
// 仅当某个工位被选中(九宫格已展开/选择)时才跳转,默认 default 跳过。
|
|
if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope)) break;
|
|
|
|
// 记录界面不是填到全局的 ShellViewManager,而是填到该工位专属的 region。
|
|
var monitorRegion = _globalInfo.CurrentScope;
|
|
if (!_regionManager.Regions.ContainsRegionWithName(monitorRegion)) break;
|
|
|
|
var monitorParameters = new NavigationParameters { { "Name", monitorRegion } };
|
|
_regionManager.RequestNavigate(monitorRegion, "MonitorView", monitorParameters);
|
|
IsLeftDrawerOpen = false;
|
|
break;
|
|
case "记录界面":
|
|
if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope)) break;
|
|
|
|
// 记录界面不是填到全局的 ShellViewManager,而是填到该工位专属的 region。
|
|
var recordRegion = _globalInfo.CurrentScope;
|
|
if (!_regionManager.Regions.ContainsRegionWithName(recordRegion)) break;
|
|
|
|
var recordParameters = new NavigationParameters { { "Name", recordRegion } };
|
|
_regionManager.RequestNavigate(recordRegion, "RecordView", recordParameters);
|
|
IsLeftDrawerOpen = false;
|
|
break;
|
|
case "设置界面":
|
|
if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope)) break;
|
|
|
|
// 记录界面不是填到全局的 ShellViewManager,而是填到该工位专属的 region。
|
|
var settingRegion = _globalInfo.CurrentScope;
|
|
if (!_regionManager.Regions.ContainsRegionWithName(settingRegion)) break;
|
|
|
|
var settingParameters = new NavigationParameters { { "Name", settingRegion } };
|
|
_regionManager.RequestNavigate(settingRegion, "SettingView", settingParameters);
|
|
IsLeftDrawerOpen = false;
|
|
break;
|
|
case "台架移动界面":
|
|
if (_globalInfo.CurrentScope != "default") return;
|
|
_regionManager.RequestNavigate("ShellViewManager", "BenchMovementView");
|
|
break;
|
|
case "曲线回读界面":
|
|
if (_globalInfo.CurrentScope != "default") return;
|
|
_regionManager.RequestNavigate("ShellViewManager", "CurveRecallView");
|
|
break;
|
|
case "测试报告导出界面":
|
|
if (_globalInfo.CurrentScope != "default") return;
|
|
_regionManager.RequestNavigate("ShellViewManager", "ExportView");
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void LeftDrawerOpen()
|
|
{
|
|
IsLeftDrawerOpen = true;
|
|
}
|
|
|
|
private void MinimizeWindow(Window window)
|
|
{
|
|
if (window != null)
|
|
window.WindowState = WindowState.Minimized;
|
|
}
|
|
|
|
private void MaximizeWindow(Window window)
|
|
{
|
|
if (window != null)
|
|
{
|
|
window.WindowState = window.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
|
|
}
|
|
}
|
|
|
|
private void CloseWindow(Window window)
|
|
{
|
|
window?.Close();
|
|
}
|
|
#endregion
|
|
}
|
|
} |