#0024 修复浮动工具箱

This commit is contained in:
zhengxuan.zhang
2026-03-15 00:47:51 +08:00
parent 1b1b0872c7
commit 1cc4b5e4d0
5 changed files with 133 additions and 94 deletions
@@ -141,22 +141,30 @@ namespace XplorePlane.ViewModels
private void AddOperator(string operatorKey) private void AddOperator(string operatorKey)
{ {
Serilog.Log.Debug("AddOperator 被调用,operatorKey={OperatorKey}", operatorKey);
if (string.IsNullOrWhiteSpace(operatorKey)) if (string.IsNullOrWhiteSpace(operatorKey))
{ {
StatusMessage = "算子键不能为空"; StatusMessage = "算子键不能为空";
Serilog.Log.Warning("AddOperator 失败:operatorKey 为空");
return; return;
} }
var available = _imageProcessingService.GetAvailableProcessors(); var available = _imageProcessingService.GetAvailableProcessors();
Serilog.Log.Debug("可用算子数量:{Count},包含 {Key}{Contains}",
available.Count(), operatorKey, available.Contains(operatorKey));
if (!available.Contains(operatorKey)) if (!available.Contains(operatorKey))
{ {
StatusMessage = $"算子 '{operatorKey}' 未注册"; StatusMessage = $"算子 '{operatorKey}' 未注册";
Serilog.Log.Warning("AddOperator 失败:算子 {Key} 未注册", operatorKey);
return; return;
} }
if (PipelineNodes.Count >= MaxPipelineLength) if (PipelineNodes.Count >= MaxPipelineLength)
{ {
StatusMessage = $"流水线节点数已达上限({MaxPipelineLength}"; StatusMessage = $"流水线节点数已达上限({MaxPipelineLength}";
Serilog.Log.Warning("AddOperator 失败:节点数已达上限 {Max}", MaxPipelineLength);
return; return;
} }
@@ -167,6 +175,8 @@ namespace XplorePlane.ViewModels
}; };
LoadNodeParameters(node); LoadNodeParameters(node);
PipelineNodes.Add(node); PipelineNodes.Add(node);
Serilog.Log.Information("节点已添加到 PipelineNodes{Key} ({DisplayName}),当前节点数={Count}",
operatorKey, displayName, PipelineNodes.Count);
StatusMessage = $"已添加算子:{displayName}"; StatusMessage = $"已添加算子:{displayName}";
TriggerDebouncedExecution(); TriggerDebouncedExecution();
} }
@@ -1,14 +1,18 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Input;
using Prism.Ioc; using Prism.Ioc;
using Telerik.Windows.DragDrop; using Serilog;
using XplorePlane.ViewModels; using XplorePlane.ViewModels;
namespace XplorePlane.Views namespace XplorePlane.Views
{ {
public partial class OperatorToolboxView : UserControl public partial class OperatorToolboxView : UserControl
{ {
private const string DragFormat = "OperatorDescriptor"; public const string DragFormat = "PipelineOperatorKey";
private Point _dragStartPoint;
private bool _isDragging;
public OperatorToolboxView() public OperatorToolboxView()
{ {
@@ -21,29 +25,55 @@ namespace XplorePlane.Views
if (DataContext == null) if (DataContext == null)
DataContext = ContainerLocator.Container.Resolve<OperatorToolboxViewModel>(); DataContext = ContainerLocator.Container.Resolve<OperatorToolboxViewModel>();
// 启用拖拽源 + 注册拖拽初始化事件 ToolboxListBox.PreviewMouseLeftButtonDown += OnPreviewMouseDown;
DragDropManager.SetAllowDrag(ToolboxListBox, true); ToolboxListBox.PreviewMouseMove += OnPreviewMouseMove;
DragDropManager.AddDragInitializeHandler(ToolboxListBox, OnDragInitialize, true); Log.Debug("OperatorToolboxView 原生拖拽源已注册");
} }
private void OnDragInitialize(object sender, DragInitializeEventArgs e) private void OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{ {
if (ToolboxListBox.SelectedItem is OperatorDescriptor descriptor) _dragStartPoint = e.GetPosition(null);
{ _isDragging = false;
e.AllowedEffects = System.Windows.DragDropEffects.Copy;
DragDropPayloadManager.SetData(e.Data, DragFormat, descriptor);
e.DragVisual = new System.Windows.Controls.TextBlock
{
Text = descriptor.DisplayName,
Padding = new Thickness(6, 3, 6, 3),
Background = System.Windows.Media.Brushes.LightBlue,
FontFamily = new System.Windows.Media.FontFamily("Microsoft YaHei UI"),
FontSize = 11
};
} }
else
private void OnPreviewMouseMove(object sender, MouseEventArgs e)
{ {
e.Cancel = true; if (e.LeftButton != MouseButtonState.Pressed) return;
var pos = e.GetPosition(null);
var diff = pos - _dragStartPoint;
if (!_isDragging
&& (System.Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance
|| System.Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
{
_isDragging = true;
// 从鼠标位置 HitTest 找到 OperatorDescriptor
var hitResult = System.Windows.Media.VisualTreeHelper.HitTest(
ToolboxListBox, e.GetPosition(ToolboxListBox));
OperatorDescriptor? descriptor = null;
var node = hitResult?.VisualHit as DependencyObject;
while (node != null)
{
if (node is FrameworkElement fe && fe.DataContext is OperatorDescriptor d)
{
descriptor = d;
break;
}
node = System.Windows.Media.VisualTreeHelper.GetParent(node);
}
if (descriptor == null)
{
Log.Warning("拖拽初始化失败:HitTest 未命中算子项");
return;
}
Log.Information("开始拖拽算子:{OperatorKey} ({DisplayName})", descriptor.Key, descriptor.DisplayName);
var data = new DataObject(DragFormat, descriptor.Key);
DragDrop.DoDragDrop(ToolboxListBox, data, DragDropEffects.Copy);
} }
} }
} }
@@ -17,7 +17,7 @@
<FontFamily x:Key="CsdFont">Microsoft YaHei UI</FontFamily> <FontFamily x:Key="CsdFont">Microsoft YaHei UI</FontFamily>
<!-- 节点项样式 --> <!-- 节点项样式 -->
<Style x:Key="PipelineNodeItemStyle" TargetType="telerik:RadListBoxItem"> <Style x:Key="PipelineNodeItemStyle" TargetType="ListBoxItem">
<Setter Property="Padding" Value="0" /> <Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="0" /> <Setter Property="Margin" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" /> <Setter Property="HorizontalContentAlignment" Value="Stretch" />
@@ -45,7 +45,7 @@
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="36" /> <RowDefinition Height="40" />
<RowDefinition Height="2*" /> <RowDefinition Height="2*" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
@@ -110,19 +110,19 @@
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal"> <StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<Button <Button
Command="{Binding NewPipelineCommand}" Command="{Binding NewPipelineCommand}"
Content="新" Content="新"
Style="{StaticResource ToolbarBtn}" Style="{StaticResource ToolbarBtn}"
ToolTip="新建流水线" /> ToolTip="新建流水线" />
<Button <Button
Command="{Binding SavePipelineCommand}" Command="{Binding SavePipelineCommand}"
Content="存" Content="存"
Style="{StaticResource ToolbarBtn}" Style="{StaticResource ToolbarBtn}"
ToolTip="保存流水线" /> ToolTip="保存流水线" />
<Button <Button
Command="{Binding SaveAsPipelineCommand}" Command="{Binding SaveAsPipelineCommand}"
Content="另" Content="另存为"
Style="{StaticResource ToolbarBtn}" Style="{StaticResource ToolbarBtn}"
ToolTip="另存为" /> ToolTip="另存为" Width="43" />
<Button <Button
Command="{Binding ExecutePipelineCommand}" Command="{Binding ExecutePipelineCommand}"
Content="▶" Content="▶"
@@ -145,83 +145,52 @@
<!-- 流水线节点列表(拖拽目标) --> <!-- 流水线节点列表(拖拽目标) -->
<telerik:RadListBox <ListBox
x:Name="PipelineListBox" x:Name="PipelineListBox"
Grid.Row="2" Grid.Row="2"
Background="Transparent" Background="Transparent"
BorderThickness="0" BorderThickness="0"
ItemContainerStyle="{StaticResource PipelineNodeItemStyle}" ItemContainerStyle="{StaticResource PipelineNodeItemStyle}"
ItemsSource="{Binding PipelineNodes}" ItemsSource="{Binding PipelineNodes}"
SelectedItem="{Binding SelectedNode, Mode=TwoWay}"> SelectedItem="{Binding SelectedNode, Mode=TwoWay}"
<telerik:RadListBox.ItemTemplate> ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid> <Grid MinHeight="44">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="20" /> <ColumnDefinition Width="28" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- 竖线连接 --> <!-- 竖线连接 + 节点方块 -->
<Canvas Grid.Column="0" Width="20"> <Grid Grid.Column="0">
<Line <Line Stroke="#5B9BD5" StrokeThickness="2"
Stroke="#5B9BD5" X1="14" X2="14" Y1="0" Y2="44"
StrokeThickness="2" HorizontalAlignment="Left" />
X1="10" <Border Width="16" Height="16"
X2="10" Background="White"
Y1="0" BorderBrush="#5B9BD5" BorderThickness="1.5"
Y2="40" /> CornerRadius="2"
<Rectangle VerticalAlignment="Center" HorizontalAlignment="Center" />
Canvas.Left="3" </Grid>
Canvas.Top="12"
Width="14"
Height="14"
Fill="White"
RadiusX="2"
RadiusY="2"
Stroke="#5B9BD5"
StrokeThickness="1.5" />
</Canvas>
<!-- 节点行 --> <!-- 节点行 -->
<Border <Border Grid.Column="1" Padding="6,8">
Grid.Column="1"
Padding="6,8"
BorderBrush="Transparent"
BorderThickness="0">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Background" Value="{StaticResource AccentBlue}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<Border <Border Width="28" Height="28" Margin="0,0,8,0"
Width="28" Background="#E8F0FE" CornerRadius="3">
Height="28" <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
Margin="0,0,8,0" FontSize="13" Text="⚙" />
Background="#E8F0FE"
CornerRadius="3">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="13"
Text="⚙" />
</Border> </Border>
<TextBlock <TextBlock VerticalAlignment="Center"
VerticalAlignment="Center" FontFamily="Microsoft YaHei UI" FontSize="12"
FontFamily="Microsoft YaHei UI"
FontSize="12"
Text="{Binding DisplayName}" /> Text="{Binding DisplayName}" />
</StackPanel> </StackPanel>
</Border> </Border>
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</telerik:RadListBox.ItemTemplate> </ListBox.ItemTemplate>
</telerik:RadListBox> </ListBox>
<!-- 分隔线 --> <!-- 分隔线 -->
<Rectangle <Rectangle
Grid.Row="3" Grid.Row="3"
@@ -1,15 +1,13 @@
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using Prism.Ioc; using Prism.Ioc;
using Telerik.Windows.DragDrop; using Serilog;
using XplorePlane.ViewModels; using XplorePlane.ViewModels;
namespace XplorePlane.Views namespace XplorePlane.Views
{ {
public partial class PipelineEditorView : UserControl public partial class PipelineEditorView : UserControl
{ {
private const string DragFormat = "OperatorDescriptor";
public PipelineEditorView() public PipelineEditorView()
{ {
InitializeComponent(); InitializeComponent();
@@ -21,19 +19,47 @@ namespace XplorePlane.Views
if (DataContext == null) if (DataContext == null)
DataContext = ContainerLocator.Container.Resolve<PipelineEditorViewModel>(); DataContext = ContainerLocator.Container.Resolve<PipelineEditorViewModel>();
// 启用拖拽目标 + 注册 Drop 事件
PipelineListBox.AllowDrop = true; PipelineListBox.AllowDrop = true;
DragDropManager.AddDropHandler(PipelineListBox, OnOperatorDropped, true); PipelineListBox.Drop += OnOperatorDropped;
PipelineListBox.DragOver += OnDragOver;
Log.Debug("PipelineEditorView 原生 Drop 目标已注册");
} }
private void OnOperatorDropped(object sender, Telerik.Windows.DragDrop.DragEventArgs e) private void OnDragOver(object sender, DragEventArgs e)
{ {
if (DataContext is not PipelineEditorViewModel vm) return; if (e.Data.GetDataPresent(OperatorToolboxView.DragFormat))
e.Effects = DragDropEffects.Copy;
else
e.Effects = DragDropEffects.None;
e.Handled = true;
}
var descriptor = DragDropPayloadManager.GetDataFromObject(e.Data, DragFormat) as OperatorDescriptor; private void OnOperatorDropped(object sender, DragEventArgs e)
if (descriptor == null) return; {
if (DataContext is not PipelineEditorViewModel vm)
{
Log.Warning("Drop 事件触发但 DataContext 不是 PipelineEditorViewModel");
return;
}
vm.AddOperatorCommand.Execute(descriptor.Key); if (!e.Data.GetDataPresent(OperatorToolboxView.DragFormat))
{
Log.Warning("Drop 事件触发但数据中无 {Format}", OperatorToolboxView.DragFormat);
return;
}
var operatorKey = e.Data.GetData(OperatorToolboxView.DragFormat) as string;
if (string.IsNullOrEmpty(operatorKey))
{
Log.Warning("Drop 事件触发但 OperatorKey 为空");
return;
}
Log.Information("算子已放入流水线:{OperatorKey}VM HashCode={Hash},当前节点数(执行前)={Count}",
operatorKey, vm.GetHashCode(), vm.PipelineNodes.Count);
vm.AddOperatorCommand.Execute(operatorKey);
Log.Information("AddOperator 执行后节点数={Count}PipelineListBox.Items.Count={ItemsCount}",
vm.PipelineNodes.Count, PipelineListBox.Items.Count);
e.Handled = true; e.Handled = true;
} }
} }
+4
View File
@@ -16,4 +16,8 @@
2、硬件层射线源的集成 √ 2、硬件层射线源的集成 √
3、图像层集成,包括复刻一个示例界面,优化界面布局及算子中文 √ 3、图像层集成,包括复刻一个示例界面,优化界面布局及算子中文 √
4、浮动图像处理工具箱调研 √ 4、浮动图像处理工具箱调研 √
5、修复图像工具箱拖拽事件,流水线列表没有生成对应的控件
5、各窗体间数据流的传递,全局数据结构的设计 5、各窗体间数据流的传递,全局数据结构的设计