Files
ADP/TestingModule/ViewModels/StepsManagerViewModel.cs
T
2026-08-07 16:30:13 +08:00

462 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UIShare.PubEvent;
using UIShare.UIViewModel;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Xml;
using UIShare.GlobalVariable;
using UIShare.ViewModelBase;
using Prism.Events;
namespace TestingModule.ViewModels
{
public class StepsManagerViewModel:NavigateViewModelBase, IDisposable
{
#region 属性
private string _Title;
public string Title
{
get => _Title;
set => SetProperty(ref _Title, value);
}
private bool _Admin;
public bool Admin
{
get => _Admin;
set => SetProperty(ref _Admin, value);
}
private int _SelectedTabIndex;
public int SelectedTabIndex
{
get => _SelectedTabIndex;
set => SetProperty(ref _SelectedTabIndex, value);
}
private string _selectedTabHeader;
public string SelectedTabHeader
{
get { return _selectedTabHeader; }
set { SetProperty(ref _selectedTabHeader, value); }
}
private List<StepVM> _SelectedItems;
public List<StepVM> SelectedItems
{
get { return _SelectedItems; }
set { SetProperty(ref _SelectedItems, value); }
}
private StepVM _SelectedStep;
public StepVM SelectedStep
{
get => _SelectedStep;
set
{
if (SetProperty(ref _SelectedStep, value))
{
_ScopedContext.SelectedStep = value;
}
}
}
public ProgramVM Program
{
get => _ScopedContext.Program;
set
{
if (_ScopedContext.Program != value)
{
_ScopedContext.Program = value;
RaisePropertyChanged();
}
}
}
ScopedContext _ScopedContext { get; set; }
private readonly SystemConfig _systemConfig;
private readonly GlobalInfo _globalInfo;
private List<StepVM> tmpCopyList = new List<StepVM>();
// 追踪当前订阅的集合实例,用于在集合被整体替换时重新订阅
private ObservableCollection<StepVM> _trackedStepCollection;
private ObservableCollection<StepVM> _trackedErrorStepCollection;
#endregion
#region 命令
public ICommand EditStepCommand { get;set; }
public ICommand CopyStepCommand { get;set; }
public ICommand PasteStepCommand { get;set; }
public ICommand DeleteStepCommand { get;set; }
public ICommand TabSelectionChangedCommand { get;set; }
public ICommand SelectionChangedCommand { get;set; }
public ICommand OpenSubProgramCommand { get; set; }
public ICommand GoBackCommand { get; set; }
#endregion
#region 子程序导航(主程序 / 错误程序各自独立)
// --- 主程序 Tab 导航属性 ---
public ProgramVM MainCurrentProgram => _ScopedContext.MainNav.CurrentProgram;
public string MainBreadcrumbPath => _ScopedContext.MainNav.BreadcrumbPath;
public bool MainCanGoBack => _ScopedContext.MainNav.CanGoBack;
public ObservableCollection<StepVM> MainDisplaySteps => _ScopedContext.MainNav.DisplaySteps;
// --- 错误程序 Tab 导航属性 ---
public ProgramVM ErrorCurrentProgram => _ScopedContext.ErrorNav.CurrentProgram;
public string ErrorBreadcrumbPath => _ScopedContext.ErrorNav.BreadcrumbPath;
public bool ErrorCanGoBack => _ScopedContext.ErrorNav.CanGoBack;
public ObservableCollection<StepVM> ErrorDisplaySteps => _ScopedContext.ErrorNav.DisplaySteps;
/// <summary>
/// 当前激活的导航状态(根据选中的 Tab 索引决定)
/// </summary>
private ProgramNavigationState ActiveNav => SelectedTabIndex == 1 ? _ScopedContext.ErrorNav : _ScopedContext.MainNav;
#endregion
public StepsManagerViewModel(IContainerProvider containerProvider, ScopedContext scopedContext, SystemConfig systemConfig, GlobalInfo globalInfo) : base(containerProvider)
{
_ScopedContext = scopedContext;
_systemConfig = systemConfig;
_globalInfo = globalInfo;
Title = _systemConfig.Title;
EditStepCommand = new DelegateCommand(EditStep);
CopyStepCommand = new DelegateCommand(CopyStep);
PasteStepCommand = new DelegateCommand(PasteStep);
DeleteStepCommand = new DelegateCommand(DeleteStep);
TabSelectionChangedCommand = new DelegateCommand<string>(TabSelectionChanged);
SelectionChangedCommand = new DelegateCommand<object>(SelectionChanged);
OpenSubProgramCommand = new DelegateCommand(OpenSubProgram);
GoBackCommand = new DelegateCommand(GoBack);
SubscribeStepCollections();
Program.PropertyChanged += Program_PropertyChanged;
Admin = _globalInfo.IsAdmin;
// 初始化两套导航状态
_ScopedContext.MainNav.Initialize(Program, "主程序");
_ScopedContext.ErrorNav.Initialize(Program, "错误程序");
// 订阅主程序导航状态变化 → 转发给 UI
_ScopedContext.MainNav.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(ProgramNavigationState.CurrentProgram))
{
RaisePropertyChanged(nameof(MainCurrentProgram));
RaisePropertyChanged(nameof(MainDisplaySteps));
}
else if (e.PropertyName == nameof(ProgramNavigationState.BreadcrumbPath))
RaisePropertyChanged(nameof(MainBreadcrumbPath));
else if (e.PropertyName == nameof(ProgramNavigationState.CanGoBack))
RaisePropertyChanged(nameof(MainCanGoBack));
};
// 订阅错误程序导航状态变化 → 转发给 UI
_ScopedContext.ErrorNav.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(ProgramNavigationState.CurrentProgram))
{
RaisePropertyChanged(nameof(ErrorCurrentProgram));
RaisePropertyChanged(nameof(ErrorDisplaySteps));
}
else if (e.PropertyName == nameof(ProgramNavigationState.BreadcrumbPath))
RaisePropertyChanged(nameof(ErrorBreadcrumbPath));
else if (e.PropertyName == nameof(ProgramNavigationState.CanGoBack))
RaisePropertyChanged(nameof(ErrorCanGoBack));
};
// 订阅子程序导航事件(运行时由 StepRunning 发布)
_eventAggregator.GetEvent<SubProgramNavigateEvent>()
.Subscribe(OnSubProgramNavigate, ThreadOption.UIThread, false,
payload => payload.Scope == _systemConfig.Title);
}
private void SelectionChanged(object parameter)
{
var selectedList = parameter as IList;
if (selectedList != null)
{
SelectedItems = selectedList.Cast<StepVM>().ToList();
}
}
private void TabSelectionChanged(string SelectedTabHeader)
{
_ScopedContext.SelectedStepList = SelectedTabHeader;
}
#region 委托命令
private void EditStep()
{
if (_globalInfo.IsAdmin && SelectedStep != null&& _ScopedContext.SelectedStep!=null)
{
_eventAggregator.GetEvent<EditSetpEvent>().Publish() ;
}
}
private void CopyStep()
{
if (_globalInfo.IsAdmin && SelectedItems.Any())
{
tmpCopyList.Clear();
foreach (var item in SelectedItems)
{
tmpCopyList.Add(item);
}
}
}
private void PasteStep()
{
// 权限校验并确保有可粘贴的内容
if (_globalInfo.IsAdmin && tmpCopyList.Any())
{
int insertIndex;
if (_ScopedContext.SelectedStepList == "主程序")
{
insertIndex = SelectedStep != null ? Program.StepCollection.IndexOf(SelectedStep) + 1 : Program.StepCollection.Count;
foreach (var item in tmpCopyList)
{
// 创建新副本,避免引用同一个对象,并赋予新 ID
var newStep = new StepVM(item) { ID = Guid.NewGuid() };
Program.StepCollection.Insert(insertIndex, newStep);
insertIndex++; // 递增索引,保证粘贴的多项顺序一致
}
}
else if (_ScopedContext.SelectedStepList == "错误程序")
{
insertIndex = SelectedStep != null ? Program.ErrorStepCollection.IndexOf(SelectedStep) + 1 : Program.ErrorStepCollection.Count;
foreach (var item in tmpCopyList)
{
var newStep = new StepVM(item) { ID = Guid.NewGuid() };
Program.ErrorStepCollection.Insert(insertIndex, newStep);
insertIndex++;
}
}
}
}
private void DeleteStep()
{
// 确保有选中的项
if (_globalInfo.IsAdmin && SelectedItems != null && SelectedItems.Any())
{
// 创建一个副本进行循环,防止在 Remove 过程中集合变化导致的问题
var toDelete = SelectedItems.ToList();
foreach (var item in toDelete)
{
_eventAggregator.GetEvent<DeletedStepEvent>().Publish(item.ID);
if (_ScopedContext.SelectedStepList == "主程序")
{
Program.StepCollection.Remove(item);
}
else if (_ScopedContext.SelectedStepList == "错误程序")
{
Program.ErrorStepCollection.Remove(item);
}
}
// 3. 清空 ViewModel 的选中状态,避免悬挂引用
SelectedStep = null;
SelectedItems.Clear();
_ScopedContext.SelectedStep = null;
}
}
#endregion
#region 子程序导航方法
/// <summary>
/// 编辑模式:右键打开子程序(操作当前激活的 Tab 的导航状态)
/// </summary>
private void OpenSubProgram()
{
if (SelectedStep == null || SelectedStep.StepType != "子程序" || SelectedStep.SubProgram == null)
return;
var nav = ActiveNav;
nav.NavigationStack.Push(nav.CurrentProgram);
nav.UpdateCanGoBack();
var stepName = SelectedStep.Name ?? "子程序";
nav.BreadcrumbPath = $"{nav.BreadcrumbPath} > {stepName}";
nav.CurrentProgram = SelectedStep.SubProgram;
}
/// <summary>
/// 返回上一级程序(操作当前激活的 Tab 的导航状态)
/// </summary>
private void GoBack()
{
var nav = ActiveNav;
if (nav.NavigationStack.Count == 0)
return;
var parentProgram = nav.NavigationStack.Pop();
nav.UpdateCanGoBack();
nav.CurrentProgram = parentProgram;
var parts = nav.BreadcrumbPath.Split(" > ");
nav.BreadcrumbPath = parts.Length > 1
? string.Join(" > ", parts.Take(parts.Length - 1))
: nav == _ScopedContext.MainNav ? "主程序" : "错误程序";
}
/// <summary>
/// 运行时:处理 StepRunning 发布的子程序导航事件
/// </summary>
private void OnSubProgramNavigate(SubProgramNavigatePayload payload)
{
var nav = payload.IsErrorProgram ? _ScopedContext.ErrorNav : _ScopedContext.MainNav;
if (payload.Action == NavigateAction.Enter && payload.SubProgram != null)
{
nav.NavigationStack.Push(nav.CurrentProgram);
nav.UpdateCanGoBack();
nav.CurrentProgram = payload.SubProgram;
var stepName = payload.StepName ?? "子程序";
nav.BreadcrumbPath = $"{nav.BreadcrumbPath} > {stepName}";
}
else if (payload.Action == NavigateAction.Exit)
{
if (nav.NavigationStack.Count == 0) return;
var parentProgram = nav.NavigationStack.Pop();
nav.UpdateCanGoBack();
nav.CurrentProgram = parentProgram;
var parts = nav.BreadcrumbPath.Split(" > ");
nav.BreadcrumbPath = parts.Length > 1
? string.Join(" > ", parts.Take(parts.Length - 1))
: nav == _ScopedContext.MainNav ? "主程序" : "错误程序";
}
}
#endregion
#region 辅助方法
/// <summary>
/// 订阅当前 Program 的 StepCollection 和 ErrorStepCollection 的 CollectionChanged 事件。
/// </summary>
private void SubscribeStepCollections()
{
_trackedStepCollection = Program.StepCollection;
_trackedErrorStepCollection = Program.ErrorStepCollection;
if (_trackedStepCollection != null)
_trackedStepCollection.CollectionChanged += StepCollection_CollectionChanged;
if (_trackedErrorStepCollection != null)
_trackedErrorStepCollection.CollectionChanged += StepCollection_CollectionChanged;
}
/// <summary>
/// 取消订阅当前追踪的集合事件。
/// </summary>
private void UnsubscribeStepCollections()
{
if (_trackedStepCollection != null)
{
_trackedStepCollection.CollectionChanged -= StepCollection_CollectionChanged;
_trackedStepCollection = null;
}
if (_trackedErrorStepCollection != null)
{
_trackedErrorStepCollection.CollectionChanged -= StepCollection_CollectionChanged;
_trackedErrorStepCollection = null;
}
}
/// <summary>
/// 当 Program 的 StepCollection / ErrorStepCollection 被整体替换时,自动重新订阅并刷新序号。
/// </summary>
private void Program_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ProgramVM.StepCollection) ||
e.PropertyName == nameof(ProgramVM.ErrorStepCollection))
{
UnsubscribeStepCollections();
SubscribeStepCollections();
// 集合被整体替换后,对新集合重新编号
Application.Current?.Dispatcher.BeginInvoke(new Action(() =>
{
for (int i = 0; i < Program.StepCollection.Count; i++)
Program.StepCollection[i].Index = i + 1;
for (int i = 0; i < Program.ErrorStepCollection.Count; i++)
Program.ErrorStepCollection[i].Index = i + 1;
}), System.Windows.Threading.DispatcherPriority.Background);
}
}
private void StepCollection_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
var collection = sender as ObservableCollection<StepVM>;
// Add/Move/Remove 都会触发,这里判断具体情形
if (e.Action == NotifyCollectionChangedAction.Add ||
e.Action == NotifyCollectionChangedAction.Move||
e.Action == NotifyCollectionChangedAction.Remove)
{
// 如果需要等 UI 更新完再处理,可也用 Dispatcher 延迟一小段时间
Application.Current?.Dispatcher.BeginInvoke(new Action(() =>
{
if(_ScopedContext.SelectedStepList=="主程序")
{
for (int i = 0; i < Program.StepCollection.Count; i++)
Program.StepCollection[i].Index = i + 1;
}
else if (_ScopedContext.SelectedStepList == "错误程序")
for (int i = 0; i < Program.ErrorStepCollection.Count; i++)
Program.ErrorStepCollection[i].Index = i + 1;
}), System.Windows.Threading.DispatcherPriority.Background);
}
}
#endregion
public void Dispose()
{
try
{
// 1. 取消订阅 CollectionChanged 事件(从追踪的实例上取消,而非可能已被替换的当前实例)
UnsubscribeStepCollections();
// 2. 取消订阅 Program.PropertyChanged
if (Program != null)
{
Program.PropertyChanged -= Program_PropertyChanged;
}
// 3. 【核心修复】必须显式退订 Prism 全局事件(AlarmEvent
// 注意:因为订阅时使用的是匿名 Lambda,最安全稳妥的退订方式是把整个事件上的当前 VM 订阅者全部注销
_eventAggregator?.GetEvent<AlarmEvent>()?.Unsubscribe(null);
// 4. 清空临时缓存集合与 UI 绑定列表,避免悬挂指针
tmpCopyList?.Clear();
tmpCopyList = null!;
SelectedItems?.Clear();
SelectedItems = null!;
// 5. 清除选中项状态引用
SelectedStep = null;
if (_ScopedContext != null)
{
_ScopedContext.SelectedStep = null;
_ScopedContext = null!; // 断开上下文引用
}
}
catch (Exception ex)
{
Logger.LoggerHelper.Error($"释放步骤管理组件(StepsManagerViewModel)资源失败: {ex.Message}");
}
}
}
}