#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)
{
Serilog.Log.Debug("AddOperator 被调用,operatorKey={OperatorKey}", operatorKey);
if (string.IsNullOrWhiteSpace(operatorKey))
{
StatusMessage = "算子键不能为空";
Serilog.Log.Warning("AddOperator 失败:operatorKey 为空");
return;
}
var available = _imageProcessingService.GetAvailableProcessors();
Serilog.Log.Debug("可用算子数量:{Count},包含 {Key}{Contains}",
available.Count(), operatorKey, available.Contains(operatorKey));
if (!available.Contains(operatorKey))
{
StatusMessage = $"算子 '{operatorKey}' 未注册";
Serilog.Log.Warning("AddOperator 失败:算子 {Key} 未注册", operatorKey);
return;
}
if (PipelineNodes.Count >= MaxPipelineLength)
{
StatusMessage = $"流水线节点数已达上限({MaxPipelineLength}";
Serilog.Log.Warning("AddOperator 失败:节点数已达上限 {Max}", MaxPipelineLength);
return;
}
@@ -167,6 +175,8 @@ namespace XplorePlane.ViewModels
};
LoadNodeParameters(node);
PipelineNodes.Add(node);
Serilog.Log.Information("节点已添加到 PipelineNodes{Key} ({DisplayName}),当前节点数={Count}",
operatorKey, displayName, PipelineNodes.Count);
StatusMessage = $"已添加算子:{displayName}";
TriggerDebouncedExecution();
}
@@ -1,14 +1,18 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Prism.Ioc;
using Telerik.Windows.DragDrop;
using Serilog;
using XplorePlane.ViewModels;
namespace XplorePlane.Views
{
public partial class OperatorToolboxView : UserControl
{
private const string DragFormat = "OperatorDescriptor";
public const string DragFormat = "PipelineOperatorKey";
private Point _dragStartPoint;
private bool _isDragging;
public OperatorToolboxView()
{
@@ -21,29 +25,55 @@ namespace XplorePlane.Views
if (DataContext == null)
DataContext = ContainerLocator.Container.Resolve<OperatorToolboxViewModel>();
// 启用拖拽源 + 注册拖拽初始化事件
DragDropManager.SetAllowDrag(ToolboxListBox, true);
DragDropManager.AddDragInitializeHandler(ToolboxListBox, OnDragInitialize, true);
ToolboxListBox.PreviewMouseLeftButtonDown += OnPreviewMouseDown;
ToolboxListBox.PreviewMouseMove += OnPreviewMouseMove;
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;
}
private void OnPreviewMouseMove(object sender, MouseEventArgs e)
{
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))
{
e.AllowedEffects = System.Windows.DragDropEffects.Copy;
DragDropPayloadManager.SetData(e.Data, DragFormat, descriptor);
e.DragVisual = new System.Windows.Controls.TextBlock
_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)
{
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
{
e.Cancel = true;
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>
<!-- 节点项样式 -->
<Style x:Key="PipelineNodeItemStyle" TargetType="telerik:RadListBoxItem">
<Style x:Key="PipelineNodeItemStyle" TargetType="ListBoxItem">
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
@@ -45,7 +45,7 @@
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="36" />
<RowDefinition Height="40" />
<RowDefinition Height="2*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
@@ -110,19 +110,19 @@
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
<Button
Command="{Binding NewPipelineCommand}"
Content="新"
Content="新"
Style="{StaticResource ToolbarBtn}"
ToolTip="新建流水线" />
<Button
Command="{Binding SavePipelineCommand}"
Content="存"
Content="存"
Style="{StaticResource ToolbarBtn}"
ToolTip="保存流水线" />
<Button
Command="{Binding SaveAsPipelineCommand}"
Content="另"
Content="另存为"
Style="{StaticResource ToolbarBtn}"
ToolTip="另存为" />
ToolTip="另存为" Width="43" />
<Button
Command="{Binding ExecutePipelineCommand}"
Content="▶"
@@ -145,83 +145,52 @@
<!-- 流水线节点列表(拖拽目标) -->
<telerik:RadListBox
<ListBox
x:Name="PipelineListBox"
Grid.Row="2"
Background="Transparent"
BorderThickness="0"
ItemContainerStyle="{StaticResource PipelineNodeItemStyle}"
ItemsSource="{Binding PipelineNodes}"
SelectedItem="{Binding SelectedNode, Mode=TwoWay}">
<telerik:RadListBox.ItemTemplate>
SelectedItem="{Binding SelectedNode, Mode=TwoWay}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid MinHeight="44">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="28" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- 竖线连接 -->
<Canvas Grid.Column="0" Width="20">
<Line
Stroke="#5B9BD5"
StrokeThickness="2"
X1="10"
X2="10"
Y1="0"
Y2="40" />
<Rectangle
Canvas.Left="3"
Canvas.Top="12"
Width="14"
Height="14"
Fill="White"
RadiusX="2"
RadiusY="2"
Stroke="#5B9BD5"
StrokeThickness="1.5" />
</Canvas>
<!-- 竖线连接 + 节点方块 -->
<Grid Grid.Column="0">
<Line Stroke="#5B9BD5" StrokeThickness="2"
X1="14" X2="14" Y1="0" Y2="44"
HorizontalAlignment="Left" />
<Border Width="16" Height="16"
Background="White"
BorderBrush="#5B9BD5" BorderThickness="1.5"
CornerRadius="2"
VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>
<!-- 节点行 -->
<Border
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>
<Border Grid.Column="1" Padding="6,8">
<StackPanel Orientation="Horizontal">
<Border
Width="28"
Height="28"
Margin="0,0,8,0"
Background="#E8F0FE"
CornerRadius="3">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="13"
Text="⚙" />
<Border Width="28" Height="28" Margin="0,0,8,0"
Background="#E8F0FE" CornerRadius="3">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
FontSize="13" Text="⚙" />
</Border>
<TextBlock
VerticalAlignment="Center"
FontFamily="Microsoft YaHei UI"
FontSize="12"
Text="{Binding DisplayName}" />
<TextBlock VerticalAlignment="Center"
FontFamily="Microsoft YaHei UI" FontSize="12"
Text="{Binding DisplayName}" />
</StackPanel>
</Border>
</Grid>
</DataTemplate>
</telerik:RadListBox.ItemTemplate>
</telerik:RadListBox>
</ListBox.ItemTemplate>
</ListBox>
<!-- 分隔线 -->
<Rectangle
Grid.Row="3"
@@ -1,15 +1,13 @@
using System.Windows;
using System.Windows.Controls;
using Prism.Ioc;
using Telerik.Windows.DragDrop;
using Serilog;
using XplorePlane.ViewModels;
namespace XplorePlane.Views
{
public partial class PipelineEditorView : UserControl
{
private const string DragFormat = "OperatorDescriptor";
public PipelineEditorView()
{
InitializeComponent();
@@ -21,19 +19,47 @@ namespace XplorePlane.Views
if (DataContext == null)
DataContext = ContainerLocator.Container.Resolve<PipelineEditorViewModel>();
// 启用拖拽目标 + 注册 Drop 事件
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;
if (descriptor == null) return;
private void OnOperatorDropped(object sender, DragEventArgs e)
{
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;
}
}
+4
View File
@@ -16,4 +16,8 @@
2、硬件层射线源的集成 √
3、图像层集成,包括复刻一个示例界面,优化界面布局及算子中文 √
4、浮动图像处理工具箱调研 √
5、修复图像工具箱拖拽事件,流水线列表没有生成对应的控件
5、各窗体间数据流的传递,全局数据结构的设计