463 lines
18 KiB
C#
463 lines
18 KiB
C#
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;
|
|
using Prism.Commands;
|
|
|
|
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 => _ScopedContext.ActiveNav;
|
|
|
|
#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 全局事件
|
|
_eventAggregator?.GetEvent<AlarmEvent>()?.Unsubscribe(null);
|
|
_eventAggregator?.GetEvent<SubProgramNavigateEvent>()?.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}");
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|