2 Commits

Author SHA1 Message Date
zhengxuan.zhang 2969ada965 延时节点增加进度条 2026-04-28 10:44:30 +08:00
zhengxuan.zhang 06714f819f CNC运行与底部状态栏提醒 2026-04-28 10:28:41 +08:00
4 changed files with 133 additions and 5 deletions
@@ -3,6 +3,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using XP.Common.GeneralForm.Views;
using XP.Common.Logging.Interfaces;
using XplorePlane.Models;
using XplorePlane.Services.InspectionResults;
@@ -71,7 +72,7 @@ namespace XplorePlane.Services.Cnc
case WaitDelayNode waitNode:
try
{
await Task.Delay(waitNode.DelayMilliseconds, cancellationToken);
await ExecuteWaitDelayWithProgressAsync(waitNode, cancellationToken);
}
catch (OperationCanceledException)
{
@@ -160,5 +161,49 @@ namespace XplorePlane.Services.Cnc
"Failed to complete inspection run '{0}'", runId);
}
}
private static async Task ExecuteWaitDelayWithProgressAsync(WaitDelayNode waitNode, CancellationToken cancellationToken)
{
int totalMs = waitNode.DelayMilliseconds;
if (totalMs <= 0)
return;
const int tickMs = 50;
ProgressWindow progressWindow = null;
await Application.Current.Dispatcher.InvokeAsync(() =>
{
progressWindow = new ProgressWindow(
title: "延时等待",
message: $"节点:{waitNode.Name} 等待 {totalMs / 1000.0:F1} 秒",
isCancelable: false);
progressWindow.Owner = Application.Current.MainWindow;
progressWindow.Show();
});
try
{
int elapsed = 0;
while (elapsed < totalMs)
{
cancellationToken.ThrowIfCancellationRequested();
int remaining = totalMs - elapsed;
int delay = Math.Min(tickMs, remaining);
await Task.Delay(delay, cancellationToken);
elapsed += delay;
double pct = Math.Min(100.0 * elapsed / totalMs, 100.0);
double remainSec = Math.Max(0, (totalMs - elapsed) / 1000.0);
string msg = $"节点:{waitNode.Name} 剩余 {remainSec:F1} 秒";
progressWindow?.UpdateProgress(msg, pct);
}
}
finally
{
progressWindow?.Close();
}
}
}
}
@@ -42,6 +42,9 @@ namespace XplorePlane.ViewModels.Cnc
private bool _pendingInsertAfterAnchor;
private CancellationTokenSource _cts;
private bool _isRunning;
private string _statusMessage = "就绪";
private string _executionError;
private bool _hasExecutionError;
public CncEditorViewModel(
ICncProgramService cncProgramService,
@@ -159,6 +162,24 @@ namespace XplorePlane.ViewModels.Cnc
}
}
public string StatusMessage
{
get => _statusMessage;
private set => SetProperty(ref _statusMessage, value);
}
public string ExecutionError
{
get => _executionError;
private set => SetProperty(ref _executionError, value);
}
public bool HasExecutionError
{
get => _hasExecutionError;
private set => SetProperty(ref _hasExecutionError, value);
}
public DelegateCommand InsertReferencePointCommand { get; }
public DelegateCommand InsertSaveNodeWithImageCommand { get; }
public DelegateCommand InsertSaveNodeCommand { get; }
@@ -435,14 +456,28 @@ namespace XplorePlane.ViewModels.Cnc
{
_cts = new CancellationTokenSource();
IsRunning = true;
HasExecutionError = false;
ExecutionError = null;
StatusMessage = $"正在执行:{_currentProgram?.Name ?? ""}(共 {_currentProgram?.Nodes?.Count ?? 0} 个节点)";
try
{
var progress = new Progress<CncNodeExecutionProgress>(OnExecutionProgress);
await _cncExecutionService.ExecuteAsync(_currentProgram, progress, _cts.Token);
if (_cts.IsCancellationRequested)
StatusMessage = "执行已停止";
else
StatusMessage = $"执行完成:{_currentProgram?.Name}";
}
catch (OperationCanceledException)
{
StatusMessage = "执行已取消";
}
catch (Exception ex)
{
_logger.Error(ex, "CNC execution failed");
ExecutionError = ex.Message;
HasExecutionError = true;
StatusMessage = $"执行失败:{ex.Message}";
}
finally
{
@@ -462,7 +497,17 @@ namespace XplorePlane.ViewModels.Cnc
{
var nodeVm = Nodes.FirstOrDefault(n => n.Id == progress.NodeId);
if (nodeVm != null)
{
nodeVm.ExecutionState = progress.State;
if (progress.State == NodeExecutionState.Running)
StatusMessage = $"正在执行节点:{nodeVm.Name}{nodeVm.Index + 1}/{_currentProgram?.Nodes?.Count ?? 0}";
else if (progress.State == NodeExecutionState.Failed)
{
HasExecutionError = true;
ExecutionError = $"节点 [{nodeVm.Name}] 执行失败";
StatusMessage = $"错误:节点 [{nodeVm.Name}] 执行失败";
}
}
}
private void ResetAllNodeStates()
@@ -485,6 +530,8 @@ namespace XplorePlane.ViewModels.Cnc
DeleteNodeCommand.RaiseCanExecuteChanged();
MoveNodeUpCommand.RaiseCanExecuteChanged();
MoveNodeDownCommand.RaiseCanExecuteChanged();
RunCncCommand.RaiseCanExecuteChanged();
StopCncCommand.RaiseCanExecuteChanged();
}
private void OnProgramEdited()
@@ -557,6 +604,7 @@ namespace XplorePlane.ViewModels.Cnc
: Nodes.LastOrDefault();
_preferredSelectedNodeId = null;
RaiseEditCommandsCanExecuteChanged();
}
private void NormalizeDefaultNodeNamesInCurrentProgram()
+23 -2
View File
@@ -57,6 +57,20 @@ namespace XplorePlane.ViewModels
_cncPageView = new CncPageView { DataContext = _cncEditorViewModel };
_mainViewportService.StateChanged += OnMainViewportStateChanged;
_cncEditorViewModel.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(CncEditorViewModel.StatusMessage))
RaisePropertyChanged(nameof(CncStatusMessage));
else if (e.PropertyName == nameof(CncEditorViewModel.HasExecutionError))
RaisePropertyChanged(nameof(CncHasExecutionError));
else if (e.PropertyName == nameof(CncEditorViewModel.IsRunning))
{
RunCncCommand.RaiseCanExecuteChanged();
StopCncCommand.RaiseCanExecuteChanged();
}
};
_cncEditorViewModel.RunCncCommand.CanExecuteChanged += (s, e) => RunCncCommand.RaiseCanExecuteChanged();
_cncEditorViewModel.StopCncCommand.CanExecuteChanged += (s, e) => StopCncCommand.RaiseCanExecuteChanged();
NavigationTree = new ObservableCollection<object>();
@@ -87,8 +101,12 @@ namespace XplorePlane.ViewModels
InsertSaveNodeCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertSaveNodeCommand.Execute()));
InsertPauseDialogCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertPauseDialogCommand.Execute()));
InsertWaitDelayCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertWaitDelayCommand.Execute()));
RunCncCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.RunCncCommand.Execute()));
StopCncCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.StopCncCommand.Execute()));
RunCncCommand = new DelegateCommand(
() => ExecuteCncEditorAction(vm => vm.RunCncCommand.Execute()),
() => _cncEditorViewModel.RunCncCommand.CanExecute());
StopCncCommand = new DelegateCommand(
() => ExecuteCncEditorAction(vm => vm.StopCncCommand.Execute()),
() => _cncEditorViewModel.StopCncCommand.CanExecute());
PointDistanceMeasureCommand = new DelegateCommand(ExecutePointDistanceMeasure);
PointLineDistanceMeasureCommand = new DelegateCommand(ExecutePointLineDistanceMeasure);
@@ -121,6 +139,9 @@ namespace XplorePlane.ViewModels
set => SetProperty(ref _licenseInfo, value);
}
public string CncStatusMessage => _cncEditorViewModel.StatusMessage;
public bool CncHasExecutionError => _cncEditorViewModel.HasExecutionError;
public ObservableCollection<object> NavigationTree { get; set; }
public DelegateCommand NavigateHomeCommand { get; set; }
+16 -2
View File
@@ -106,6 +106,7 @@
<StackPanel>
<telerik:RadRibbonButton
telerik:ScreenTip.Title="运行"
Command="{Binding RunCncCommand}"
Size="Large"
SmallImage="/Assets/Icons/run.png"
Text="运行" />
@@ -114,6 +115,7 @@
<telerik:RadRibbonButton
telerik:ScreenTip.Description="停止"
telerik:ScreenTip.Title="停止"
Command="{Binding StopCncCommand}"
Size="Large"
SmallImage="/Assets/Icons/stop.png"
Text="停止" />
@@ -554,7 +556,19 @@
FontFamily="Microsoft YaHei UI"
FontSize="11"
Foreground="White"
Text="就绪" />
Text="{Binding CncStatusMessage}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White" />
<Style.Triggers>
<DataTrigger Binding="{Binding CncHasExecutionError}" Value="True">
<Setter Property="Foreground" Value="#FF9090" />
<Setter Property="FontWeight" Value="SemiBold" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock
Grid.Column="1"
@@ -563,7 +577,7 @@
FontFamily="Consolas"
FontSize="11"
Foreground="White"
Text="x: 0 y: 0 RGB: 0 0 0" />
Text="x: 0 y: 0" />
</Grid>
</Border>
</Grid>