当因为算子参数输入不合理,或者执行异常,要在状态栏显示

This commit is contained in:
zhengxuan.zhang
2026-04-23 17:04:41 +08:00
parent 338358a71c
commit 5ae5963353
4 changed files with 124 additions and 24 deletions
@@ -37,11 +37,10 @@ namespace XplorePlane.Services
if (enabledNodes.Count == 0) if (enabledNodes.Count == 0)
return source; return source;
// 大图像预览缩放
var current = ScaleForPreview(source); var current = ScaleForPreview(source);
int total = enabledNodes.Count; var total = enabledNodes.Count;
for (int step = 0; step < total; step++) for (var step = 0; step < total; step++)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@@ -53,15 +52,14 @@ namespace XplorePlane.Services
if (invalidParameters.Count > 0) if (invalidParameters.Count > 0)
{ {
var invalidParameterText = string.Join("、", invalidParameters);
throw new PipelineExecutionException( throw new PipelineExecutionException(
$"算子 '{node.DisplayName}' 存在无效参数:{string.Join("", invalidParameters)}", $"算子 '{node.DisplayName}' 存在无效参数:{invalidParameterText}",
node.Order, node.Order,
node.OperatorKey); node.OperatorKey);
} }
var parameters = node.Parameters var parameters = node.Parameters.ToDictionary(p => p.Name, p => p.Value);
.Where(p => p.IsValueValid)
.ToDictionary(p => p.Name, p => p.Value);
try try
{ {
@@ -69,9 +67,12 @@ namespace XplorePlane.Services
current, node.OperatorKey, parameters, null, cancellationToken); current, node.OperatorKey, parameters, null, cancellationToken);
if (current == null) if (current == null)
{
throw new PipelineExecutionException( throw new PipelineExecutionException(
$"算子 '{node.OperatorKey}' 返回了空图像", $"算子 '{node.DisplayName}' 返回了空图像",
node.Order, node.OperatorKey); node.Order,
node.OperatorKey);
}
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -85,7 +86,9 @@ namespace XplorePlane.Services
{ {
throw new PipelineExecutionException( throw new PipelineExecutionException(
$"算子 '{node.DisplayName}' 执行失败:{ex.Message}", $"算子 '{node.DisplayName}' 执行失败:{ex.Message}",
node.Order, node.OperatorKey, ex); node.Order,
node.OperatorKey,
ex);
} }
progress?.Report(new PipelineProgress(step + 1, total, node.DisplayName)); progress?.Report(new PipelineProgress(step + 1, total, node.DisplayName));
@@ -102,7 +105,7 @@ namespace XplorePlane.Services
if (source.PixelWidth <= UhdThreshold && source.PixelHeight <= UhdThreshold) if (source.PixelWidth <= UhdThreshold && source.PixelHeight <= UhdThreshold)
return source; return source;
double scale = (double)PreviewMaxHeight / source.PixelHeight; var scale = (double)PreviewMaxHeight / source.PixelHeight;
if (source.PixelWidth * scale > UhdThreshold) if (source.PixelWidth * scale > UhdThreshold)
scale = (double)UhdThreshold / source.PixelWidth; scale = (double)UhdThreshold / source.PixelWidth;
@@ -34,6 +34,7 @@ namespace XplorePlane.ViewModels
private string _pipelineName = "新建流水线"; private string _pipelineName = "新建流水线";
private string _selectedDevice = string.Empty; private string _selectedDevice = string.Empty;
private bool _isExecuting; private bool _isExecuting;
private bool _isStatusError;
private string _statusMessage = string.Empty; private string _statusMessage = string.Empty;
private string _currentFilePath; private string _currentFilePath;
@@ -148,6 +149,12 @@ namespace XplorePlane.ViewModels
set => SetProperty(ref _statusMessage, value); set => SetProperty(ref _statusMessage, value);
} }
public bool IsStatusError
{
get => _isStatusError;
private set => SetProperty(ref _isStatusError, value);
}
// ── Commands ────────────────────────────────────────────────── // ── Commands ──────────────────────────────────────────────────
public DelegateCommand<string> AddOperatorCommand { get; } public DelegateCommand<string> AddOperatorCommand { get; }
@@ -329,7 +336,12 @@ namespace XplorePlane.ViewModels
vm.PropertyChanged += (_, e) => vm.PropertyChanged += (_, e) =>
{ {
if (e.PropertyName == nameof(ProcessorParameterVM.Value)) if (e.PropertyName == nameof(ProcessorParameterVM.Value))
{
if (TryReportInvalidParameters())
return;
TriggerDebouncedExecution(); TriggerDebouncedExecution();
}
}; };
node.Parameters.Add(vm); node.Parameters.Add(vm);
} }
@@ -339,36 +351,39 @@ namespace XplorePlane.ViewModels
{ {
if (SourceImage == null || IsExecuting) return; if (SourceImage == null || IsExecuting) return;
if (TryReportInvalidParameters())
return;
_executionCts?.Cancel(); _executionCts?.Cancel();
_executionCts = new CancellationTokenSource(); _executionCts = new CancellationTokenSource();
var token = _executionCts.Token; var token = _executionCts.Token;
IsExecuting = true; IsExecuting = true;
StatusMessage = "正在执行流水线..."; SetInfoStatus("正在执行流水线...");
try try
{ {
var progress = new Progress<PipelineProgress>(p => var progress = new Progress<PipelineProgress>(p =>
StatusMessage = $"执行中:{p.CurrentOperator} ({p.CurrentStep}/{p.TotalSteps})"); SetInfoStatus($"执行中:{p.CurrentOperator} ({p.CurrentStep}/{p.TotalSteps})"));
var result = await _executionService.ExecutePipelineAsync( var result = await _executionService.ExecutePipelineAsync(
PipelineNodes, SourceImage, progress, token); PipelineNodes, SourceImage, progress, token);
PreviewImage = result; PreviewImage = result;
StatusMessage = "流水线执行完成"; SetInfoStatus("流水线执行完成");
PublishPipelinePreviewUpdated(result, StatusMessage); PublishPipelinePreviewUpdated(result, StatusMessage);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
StatusMessage = "流水线执行已取消"; SetInfoStatus("流水线执行已取消");
} }
catch (PipelineExecutionException ex) catch (PipelineExecutionException ex)
{ {
StatusMessage = $"节点 '{ex.FailedOperatorKey}' 执行失败:{ex.Message}"; SetErrorStatus($"执行失败:{ex.Message}");
} }
catch (Exception ex) catch (Exception ex)
{ {
StatusMessage = $"执行错误:{ex.Message}"; SetErrorStatus($"执行错误:{ex.Message}");
} }
finally finally
{ {
@@ -376,6 +391,36 @@ namespace XplorePlane.ViewModels
} }
} }
private bool TryReportInvalidParameters()
{
var firstInvalidNode = PipelineNodes
.Where(n => n.IsEnabled)
.OrderBy(n => n.Order)
.FirstOrDefault(n => n.Parameters.Any(p => !p.IsValueValid));
if (firstInvalidNode == null)
return false;
var invalidNames = firstInvalidNode.Parameters
.Where(p => !p.IsValueValid)
.Select(p => p.DisplayName);
SetErrorStatus($"参数错误:算子 '{firstInvalidNode.DisplayName}' 的 {string.Join("", invalidNames)} 输入不合理,请修正后重试。");
return true;
}
private void SetInfoStatus(string message)
{
IsStatusError = false;
StatusMessage = message;
}
private void SetErrorStatus(string message)
{
IsStatusError = true;
StatusMessage = message;
PublishPipelinePreviewUpdated(PreviewImage ?? SourceImage, message);
}
private void LoadImage() private void LoadImage()
{ {
var dialog = new OpenFileDialog var dialog = new OpenFileDialog
@@ -53,8 +53,40 @@ namespace XplorePlane.ViewModels
set set
{ {
var normalizedValue = NormalizeValue(value); var normalizedValue = NormalizeValue(value);
if (SetProperty(ref _value, normalizedValue)) if (!Equals(_value, normalizedValue))
{
_value = normalizedValue;
ValidateValue(normalizedValue); ValidateValue(normalizedValue);
RaisePropertyChanged(nameof(Value));
RaisePropertyChanged(nameof(BoolValue));
RaisePropertyChanged(nameof(SelectedOption));
}
}
}
public bool BoolValue
{
get => ParameterType == "bool" && TryConvertToBool(_value, out var boolValue) && boolValue;
set
{
if (ParameterType == "bool")
{
Value = value;
}
}
}
public string SelectedOption
{
get => HasOptions
? Convert.ToString(_value, CultureInfo.InvariantCulture) ?? string.Empty
: string.Empty;
set
{
if (HasOptions)
{
Value = value;
}
} }
} }
@@ -285,7 +285,7 @@
FontFamily="Microsoft YaHei UI" FontFamily="Microsoft YaHei UI"
FontSize="11" FontSize="11"
ItemsSource="{Binding Options}" ItemsSource="{Binding Options}"
SelectedItem="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> SelectedItem="{Binding SelectedOption, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.Style> <ComboBox.Style>
<Style TargetType="ComboBox"> <Style TargetType="ComboBox">
<Setter Property="Visibility" Value="Collapsed" /> <Setter Property="Visibility" Value="Collapsed" />
@@ -302,7 +302,7 @@
VerticalAlignment="Center" VerticalAlignment="Center"
FontFamily="Microsoft YaHei UI" FontFamily="Microsoft YaHei UI"
FontSize="11" FontSize="11"
IsChecked="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> IsChecked="{Binding BoolValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<CheckBox.Style> <CheckBox.Style>
<Style TargetType="CheckBox"> <Style TargetType="CheckBox">
<Setter Property="Visibility" Value="Collapsed" /> <Setter Property="Visibility" Value="Collapsed" />
@@ -324,15 +324,35 @@
<Border <Border
Grid.Row="4" Grid.Row="4"
Padding="6,4" Padding="6,4"
Background="#F5F5F5"
BorderBrush="{StaticResource PanelBorder}"
BorderThickness="0,1,0,0"> BorderThickness="0,1,0,0">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="#F5F5F5" />
<Setter Property="BorderBrush" Value="{StaticResource PanelBorder}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsStatusError}" Value="True">
<Setter Property="Background" Value="#FFF1F1" />
<Setter Property="BorderBrush" Value="#D9534F" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock <TextBlock
FontFamily="{StaticResource CsdFont}" FontFamily="{StaticResource CsdFont}"
FontSize="11" FontSize="11"
Foreground="#555"
Text="{Binding StatusMessage, StringFormat='Status: {0}'}" Text="{Binding StatusMessage, StringFormat='Status: {0}'}"
TextTrimming="CharacterEllipsis" /> TextTrimming="CharacterEllipsis">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#555" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsStatusError}" Value="True">
<Setter Property="Foreground" Value="#A12A2A" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border> </Border>
</Grid> </Grid>
</Border> </Border>