diff --git a/ACP/ViewModels/ShellViewModel.cs b/ACP/ViewModels/ShellViewModel.cs index e4bfeec..af76648 100644 --- a/ACP/ViewModels/ShellViewModel.cs +++ b/ACP/ViewModels/ShellViewModel.cs @@ -44,6 +44,16 @@ namespace ACP.ViewModels private readonly DispatcherTimer _uiRefreshTimer; #endregion + #region 子程序导航 + + /// + /// 当前台架是否可以返回上一级 + /// + public bool CanGoBack => CurrentContext?.CanGoBack ?? false; + + + #endregion + #region 属性 public string Title { @@ -237,6 +247,7 @@ namespace ACP.ViewModels RaisePropertyChanged(nameof(RunIcon)); RaisePropertyChanged(nameof(RunningTime)); RaisePropertyChanged(nameof(IsTerminate)); + RaisePropertyChanged(nameof(CanGoBack)); } #region 命令处理与事件 @@ -519,6 +530,9 @@ namespace ACP.ViewModels targetContext.IsTerminate = false; } + // 清空子程序导航栈,回到根程序 + targetContext.ResetNavigation(); + if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties(); } @@ -601,6 +615,7 @@ namespace ACP.ViewModels 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); @@ -665,7 +680,7 @@ namespace ACP.ViewModels targetContext.Program.StepCollection = program.StepCollection; targetContext.Program.ErrorStepCollection = program.ErrorStepCollection; targetContext.CurrentFilePath = filePath; - + targetContext.ResetNavigation(); foreach (var item in CurrentConfig.SharedParameterList) { var parameter = targetContext?.Program?.Parameters?.FirstOrDefault(x => x.Name == item.ParameterName); diff --git a/ACP/Views/ShellView.xaml b/ACP/Views/ShellView.xaml index 8217e35..39a0696 100644 --- a/ACP/Views/ShellView.xaml +++ b/ACP/Views/ShellView.xaml @@ -244,6 +244,8 @@ Command="{Binding GetFileStringCommand}"> Foreground="Black" /> + + + /// 当前显示的程序(绑定到 DataGrid 的 ItemsSource 根)。 + /// 委托到 ScopedContext.CurrentProgram,保证 ShellViewModel 也能通过 ScopedContext 访问。 + /// + public ProgramVM CurrentProgram + { + get => _ScopedContext.CurrentProgram; + set + { + if (_ScopedContext.CurrentProgram != value) + { + _ScopedContext.CurrentProgram = value; + RaisePropertyChanged(); + } + } + } + + public string BreadcrumbPath + { + get => _ScopedContext.BreadcrumbPath; + set + { + if (_ScopedContext.BreadcrumbPath != value) + { + _ScopedContext.BreadcrumbPath = value; + RaisePropertyChanged(); + } + } + } + + public bool CanGoBack => _ScopedContext.CanGoBack; + #endregion public StepsManagerViewModel(IContainerProvider containerProvider, ScopedContext scopedContext, SystemConfig systemConfig, GlobalInfo globalInfo) : base(containerProvider) @@ -108,9 +147,31 @@ namespace TestingModule.ViewModels DeleteStepCommand = new DelegateCommand(DeleteStep); TabSelectionChangedCommand = new DelegateCommand(TabSelectionChanged); SelectionChangedCommand = new DelegateCommand(SelectionChanged); + OpenSubProgramCommand = new DelegateCommand(OpenSubProgram); + GoBackCommand = new DelegateCommand(GoBack); SubscribeStepCollections(); Program.PropertyChanged += Program_PropertyChanged; Admin = _globalInfo.IsAdmin; + + // 初始化 CurrentProgram 为根程序 + _ScopedContext.CurrentProgram = Program; + _ScopedContext.BreadcrumbPath = "主程序"; + + // 订阅 ScopedContext 属性变化,外部(ShellViewModel)修改时转发通知给 UI + _ScopedContext.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(ScopedContext.CurrentProgram)) + RaisePropertyChanged(nameof(CurrentProgram)); + else if (e.PropertyName == nameof(ScopedContext.BreadcrumbPath)) + RaisePropertyChanged(nameof(BreadcrumbPath)); + else if (e.PropertyName == nameof(ScopedContext.CanGoBack)) + RaisePropertyChanged(nameof(CanGoBack)); + }; + + // 订阅子程序导航事件(运行时由 StepRunning 发布) + _eventAggregator.GetEvent() + .Subscribe(OnSubProgramNavigate, ThreadOption.UIThread, false, + payload => payload.Scope == _systemConfig.Title); } private void SelectionChanged(object parameter) @@ -208,6 +269,72 @@ namespace TestingModule.ViewModels } #endregion + #region 子程序导航方法 + + /// + /// 编辑模式:右键打开子程序 + /// + private void OpenSubProgram() + { + if (SelectedStep == null || SelectedStep.StepType != "子程序" || SelectedStep.SubProgram == null) + return; + + // 压栈:保存当前程序 + _ScopedContext.SubProgramNavigationStack.Push(CurrentProgram); + _ScopedContext.UpdateCanGoBack(); + // 更新面包屑 + var stepName = SelectedStep.Name ?? "子程序"; + BreadcrumbPath = $"{BreadcrumbPath} > {stepName}"; + // 切换到子程序 + CurrentProgram = SelectedStep.SubProgram; + RaisePropertyChanged(nameof(CanGoBack)); + } + + /// + /// 返回上一级程序 + /// + private void GoBack() + { + if (_ScopedContext.SubProgramNavigationStack.Count == 0) + return; + + // 出栈:恢复上一级程序 + var parentProgram = _ScopedContext.SubProgramNavigationStack.Pop(); + _ScopedContext.UpdateCanGoBack(); + CurrentProgram = parentProgram; + + // 更新面包屑:去掉最后一段 + var parts = BreadcrumbPath.Split(" > "); + if (parts.Length > 1) + BreadcrumbPath = string.Join(" > ", parts.Take(parts.Length - 1)); + else + BreadcrumbPath = "主程序"; + + RaisePropertyChanged(nameof(CanGoBack)); + } + + /// + /// 运行时:处理 StepRunning 发布的子程序导航事件 + /// + private void OnSubProgramNavigate(SubProgramNavigatePayload payload) + { + if (payload.Action == NavigateAction.Enter && payload.SubProgram != null) + { + _ScopedContext.SubProgramNavigationStack.Push(CurrentProgram); + _ScopedContext.UpdateCanGoBack(); + CurrentProgram = payload.SubProgram; + var stepName = payload.StepName ?? "子程序"; + BreadcrumbPath = $"{BreadcrumbPath} > {stepName}"; + RaisePropertyChanged(nameof(CanGoBack)); + } + else if (payload.Action == NavigateAction.Exit) + { + GoBack(); + } + } + + #endregion + #region 辅助方法 /// @@ -304,9 +431,9 @@ namespace TestingModule.ViewModels Program.PropertyChanged -= Program_PropertyChanged; } - // 3. 【核心修复】必须显式退订 Prism 全局事件(AlarmEvent) - // 注意:因为订阅时使用的是匿名 Lambda,最安全稳妥的退订方式是把整个事件上的当前 VM 订阅者全部注销 + // 3. 【核心修复】必须显式退订 Prism 全局事件 _eventAggregator?.GetEvent()?.Unsubscribe(null); + _eventAggregator?.GetEvent()?.Unsubscribe(null); // 4. 清空临时缓存集合与 UI 绑定列表,避免悬挂指针 tmpCopyList?.Clear(); diff --git a/TestingModule/Views/StepsManager.xaml b/TestingModule/Views/StepsManager.xaml index e7b3fca..dcb8b75 100644 --- a/TestingModule/Views/StepsManager.xaml +++ b/TestingModule/Views/StepsManager.xaml @@ -8,11 +8,26 @@ xmlns:behaviors="clr-namespace:UIShare.Behaviors;assembly=UIShare" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:prism="http://prismlibrary.com/" + xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare" d:DesignHeight="450" d:DesignWidth="800" mc:Ignorable="d"> + + + + + + + +