Files
XplorePlane/XP.ImageProcessing.RoiControl/PolygonAdorner.cs
T
XplorePlane Developer 957157ae80 fix: 修复编译错误、BGA向导预览、算子重复插入及状态栏消息
## Compilation fixes
- Fix XAML namespace reference XplorePlane.Converters → XplorePlane.Helpers (EventLogView, StateDisplayView)
- Add missing using for ISampleTypeRepository / JsonSampleTypeRepository in App.xaml.cs
- Fix ILoggerService.Warn() calls using Exception as first arg instead of message string
- Serialize CncProgram to JSON when passing to MatrixLayout (expects string, not CncProgram object)
- Suppress CS8632 nullable annotation warnings project-wide

## BGA wizard fixes
- Fix preview not showing on step 3: force RefreshBgaPreview() when entering final step
- Add step validation in CanNext() to prevent skipping to preview with invalid inputs
- Notify NextCommand.CanExecuteChanged when inputs change
- Fix RunProgress runtime binding error: set Mode=OneWay for ProgressBar.Value

## Operator toolbox: prevent duplicate insertion
- Add static OperatorTarget property for direct dispatch to active pipeline
- PipelineEditorView registers as toolbox target on load (floating window only)
- CNC page sets initial target; PipelineEditorWindow resets to CNC pipeline on close
- '+' button inserts only to the active pipeline instead of broadcasting to all subscribers

## Pipeline editor status bar messages
- Add StatusBarMessageEvent / StatusBarMessagePayload to CommonEvents.cs
- PipelineEditorViewModel publishes status on all ops (add/remove/reorder/save/execute)
- MainViewModel subscribes and displays on main window bottom status bar
- Auto-clear after timeout (3s info, 5s error)

## docs
- Merge three architecture docs into consolidated XplorePlane-架构与结构说明.md
2026-07-24 15:59:16 +08:00

142 lines
5.1 KiB
C#

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace XP.ImageProcessing.RoiControl
{
/// <summary>
/// 多边形装饰器,用于编辑多边形顶点
/// </summary>
public class PolygonAdorner : Adorner
{
private List<ControlThumb> vertexThumbs = new List<ControlThumb>(); // 顶点控制点
private VisualCollection visualChildren;
private double scaleFactor = 1;
private Models.PolygonROI? polygonROI;
public PolygonAdorner(UIElement adornedElement, double scaleFactor = 1, Models.PolygonROI? roiModel = null)
: base(adornedElement)
{
visualChildren = new VisualCollection(this);
this.scaleFactor = scaleFactor;
this.polygonROI = roiModel;
// 使用ROI模型的Points数量而不是Polygon的Points
int pointCount = polygonROI?.Points.Count ?? 0;
// 创建顶点控制点
for (int i = 0; i < pointCount; i++)
{
var thumb = new ControlThumb();
thumb.DragDelta += HandleDrag;
thumb.DragCompleted += HandleDragCompleted;
thumb.MouseRightButtonDown += HandleRightClick;
thumb.Tag = i;
thumb.Cursor = Cursors.Hand;
vertexThumbs.Add(thumb);
visualChildren.Add(thumb);
}
// 不再创建边中点控制点 - 使用智能插入算法代替
// 用户可以直接点击画布,系统会自动找到最近的边并插入顶点
}
private void HandleDrag(object sender, DragDeltaEventArgs args)
{
Thumb? hitThumb = sender as Thumb;
if (hitThumb == null || polygonROI == null) return;
int index = (int)hitThumb.Tag;
// 直接修改ROI模型的Points
if (index < polygonROI.Points.Count)
{
Point currentPoint = polygonROI.Points[index];
Point newPoint = new Point(
currentPoint.X + args.HorizontalChange,
currentPoint.Y + args.VerticalChange
);
// 使用索引器修改ObservableCollection中的元素
polygonROI.Points[index] = newPoint;
}
// 强制重新布局
InvalidateArrange();
}
private void HandleDragCompleted(object sender, DragCompletedEventArgs args)
{
// 拖拽完成后通知模型更新
if (polygonROI != null)
{
polygonROI.OnPropertyChanged(nameof(polygonROI.Points));
}
}
private void HandleRightClick(object sender, MouseButtonEventArgs e)
{
// 右键删除顶点(至少保留3个顶点)
if (polygonROI != null && polygonROI.Points.Count > 3)
{
Thumb? hitThumb = sender as Thumb;
if (hitThumb != null)
{
int index = (int)hitThumb.Tag;
// 删除顶点 - ObservableCollection会自动触发CollectionChanged事件
// PolygonRoiCanvas会监听到这个变化并自动更新Adorner
polygonROI.Points.RemoveAt(index);
e.Handled = true;
}
}
}
protected override Size ArrangeOverride(Size finalSize)
{
// 使用ROI模型的Points而不是Polygon的Points
if (polygonROI != null)
{
double thumbSize = 12 * scaleFactor;
// 布局顶点控制点
for (int i = 0; i < vertexThumbs.Count && i < polygonROI.Points.Count; i++)
{
vertexThumbs[i].Arrange(new Rect(
polygonROI.Points[i].X - (thumbSize / 2),
polygonROI.Points[i].Y - (thumbSize / 2),
thumbSize,
thumbSize));
}
}
else
{
// 备用方案:使用Polygon的Points
Polygon poly = (Polygon)AdornedElement;
double thumbSize = 12 * scaleFactor;
for (int i = 0; i < vertexThumbs.Count && i < poly.Points.Count; i++)
{
vertexThumbs[i].Arrange(new Rect(
poly.Points[i].X - (thumbSize / 2),
poly.Points[i].Y - (thumbSize / 2),
thumbSize,
thumbSize));
}
}
return finalSize;
}
protected override int VisualChildrenCount
{ get { return visualChildren.Count; } }
protected override Visual GetVisualChild(int index)
{ return visualChildren[index]; }
}
}