将十字线以及点点距ROI实现迁移到XP.ImageProcessing.RoiControl中

This commit is contained in:
李伟
2026-04-24 09:25:36 +08:00
parent 7a0731386e
commit eefbd1d1c8
6 changed files with 383 additions and 375 deletions
@@ -0,0 +1,30 @@
using System.Windows;
namespace XP.ImageProcessing.RoiControl.Controls
{
/// <summary>测量完成事件参数</summary>
public class MeasureCompletedEventArgs : RoutedEventArgs
{
public Point P1 { get; }
public Point P2 { get; }
public double Distance { get; }
public int TotalCount { get; }
public MeasureCompletedEventArgs(RoutedEvent routedEvent, Point p1, Point p2, double distance, int totalCount)
: base(routedEvent)
{
P1 = p1; P2 = p2; Distance = distance; TotalCount = totalCount;
}
}
/// <summary>测量状态变化事件参数</summary>
public class MeasureStatusEventArgs : RoutedEventArgs
{
public string Message { get; }
public MeasureStatusEventArgs(RoutedEvent routedEvent, string message) : base(routedEvent)
{
Message = message;
}
}
}
@@ -243,6 +243,266 @@ namespace XP.ImageProcessing.RoiControl.Controls
#endregion Dependency Properties
#region Crosshair
public static readonly DependencyProperty ShowCrosshairProperty =
DependencyProperty.Register(nameof(ShowCrosshair), typeof(bool), typeof(PolygonRoiCanvas),
new PropertyMetadata(false, OnShowCrosshairChanged));
public bool ShowCrosshair
{
get => (bool)GetValue(ShowCrosshairProperty);
set => SetValue(ShowCrosshairProperty, value);
}
private Line _crosshairH, _crosshairV;
private static void OnShowCrosshairChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var c = (PolygonRoiCanvas)d;
if ((bool)e.NewValue)
c.AddCrosshair();
else
c.RemoveCrosshair();
}
private void AddCrosshair()
{
RemoveCrosshair();
double w = CanvasWidth, h = CanvasHeight;
if (w <= 0 || h <= 0) return;
_crosshairH = new Line { X1 = 0, Y1 = h / 2, X2 = w, Y2 = h / 2, Stroke = Brushes.Red, StrokeThickness = 1, Opacity = 0.7, IsHitTestVisible = false };
_crosshairV = new Line { X1 = w / 2, Y1 = 0, X2 = w / 2, Y2 = h, Stroke = Brushes.Red, StrokeThickness = 1, Opacity = 0.7, IsHitTestVisible = false };
mainCanvas.Children.Add(_crosshairH);
mainCanvas.Children.Add(_crosshairV);
}
private void RemoveCrosshair()
{
if (_crosshairH != null) { mainCanvas.Children.Remove(_crosshairH); _crosshairH = null; }
if (_crosshairV != null) { mainCanvas.Children.Remove(_crosshairV); _crosshairV = null; }
}
#endregion Crosshair
#region Measurement
public static readonly DependencyProperty IsMeasuringProperty =
DependencyProperty.Register(nameof(IsMeasuring), typeof(bool), typeof(PolygonRoiCanvas),
new PropertyMetadata(false, OnIsMeasuringChanged));
public bool IsMeasuring
{
get => (bool)GetValue(IsMeasuringProperty);
set => SetValue(IsMeasuringProperty, value);
}
private Canvas _measureOverlay;
private readonly System.Collections.Generic.List<Models.MeasureGroup> _measureGroups = new();
private Ellipse _pendingDot;
private Point? _pendingPoint;
private Ellipse _mDraggingDot;
private Models.MeasureGroup _mDraggingGroup;
private bool _mDraggingIsDot1;
private static void OnIsMeasuringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var c = (PolygonRoiCanvas)d;
if ((bool)e.NewValue)
{
c.EnsureMeasureOverlay();
}
else
{
// 退出测量模式:只清除未完成的临时点,保留已完成的测量线
if (c._pendingDot != null && c._measureOverlay != null)
{
c._measureOverlay.Children.Remove(c._pendingDot);
c._pendingDot = null;
}
c._pendingPoint = null;
}
}
private void EnsureMeasureOverlay()
{
if (_measureOverlay != null) return;
_measureOverlay = new Canvas { IsHitTestVisible = true, Background = Brushes.Transparent };
_measureOverlay.SetBinding(Canvas.WidthProperty, new System.Windows.Data.Binding("CanvasWidth") { Source = this });
_measureOverlay.SetBinding(Canvas.HeightProperty, new System.Windows.Data.Binding("CanvasHeight") { Source = this });
mainCanvas.Children.Add(_measureOverlay);
}
private void RemoveMeasureOverlay()
{
if (_measureOverlay != null)
{
mainCanvas.Children.Remove(_measureOverlay);
_measureOverlay = null;
}
_measureGroups.Clear();
_pendingDot = null;
_pendingPoint = null;
_mDraggingDot = null;
_mDraggingGroup = null;
}
/// <summary>清除所有测量绘制</summary>
public void ClearMeasurements()
{
RemoveMeasureOverlay();
}
/// <summary>获取当前测量组数量</summary>
public int MeasureCount => _measureGroups.Count;
/// <summary>处理测量模式下的画布点击</summary>
private void HandleMeasureClick(Point pos)
{
if (_measureOverlay == null) EnsureMeasureOverlay();
if (_measureOverlay == null) return;
if (!_pendingPoint.HasValue)
{
_pendingPoint = pos;
_pendingDot = CreateMeasureDot(Brushes.Red);
_measureOverlay.Children.Add(_pendingDot);
SetMeasureDotPos(_pendingDot, pos);
RaiseMeasureStatusChanged($"点点距测量 - 第一点: ({pos.X:F0}, {pos.Y:F0}),请点击第二个点");
}
else
{
var g = CreateMeasureGroup(_pendingPoint.Value, pos);
_measureGroups.Add(g);
_measureOverlay.Children.Remove(_pendingDot);
_pendingDot = null;
_pendingPoint = null;
RaiseMeasureCompleted(g);
// 完成一条测量后自动退出测量模式
IsMeasuring = false;
}
}
private Models.MeasureGroup CreateMeasureGroup(Point p1, Point p2)
{
var g = new Models.MeasureGroup { P1 = p1, P2 = p2 };
g.Line = new Line { Stroke = Brushes.Lime, StrokeThickness = 2, IsHitTestVisible = false };
_measureOverlay.Children.Add(g.Line);
g.Label = new TextBlock { Foreground = Brushes.Yellow, FontSize = 13, FontWeight = FontWeights.Bold, IsHitTestVisible = false };
_measureOverlay.Children.Add(g.Label);
g.Dot1 = CreateMeasureDot(Brushes.Red);
g.Dot2 = CreateMeasureDot(Brushes.Blue);
_measureOverlay.Children.Add(g.Dot1);
_measureOverlay.Children.Add(g.Dot2);
SetMeasureDotPos(g.Dot1, p1);
SetMeasureDotPos(g.Dot2, p2);
g.UpdateLine();
g.UpdateLabel();
return g;
}
private Ellipse CreateMeasureDot(Brush fill)
{
var dot = new Ellipse { Width = 12, Height = 12, Fill = fill, Stroke = Brushes.White, StrokeThickness = 1.5, Cursor = Cursors.Hand };
dot.SetValue(ContextMenuService.IsEnabledProperty, false);
dot.MouseLeftButtonDown += MDot_Down;
dot.MouseMove += MDot_Move;
dot.MouseLeftButtonUp += MDot_Up;
dot.PreviewMouseRightButtonUp += MDot_RightClick;
return dot;
}
private static void SetMeasureDotPos(Ellipse dot, Point pos)
{
Canvas.SetLeft(dot, pos.X - dot.Width / 2);
Canvas.SetTop(dot, pos.Y - dot.Height / 2);
}
private void MDot_Down(object sender, MouseButtonEventArgs e)
{
if (sender is not Ellipse dot) return;
foreach (var g in _measureGroups)
{
if (g.Dot1 == dot) { _mDraggingGroup = g; _mDraggingIsDot1 = true; break; }
if (g.Dot2 == dot) { _mDraggingGroup = g; _mDraggingIsDot1 = false; break; }
}
if (_mDraggingGroup != null) { _mDraggingDot = dot; dot.CaptureMouse(); e.Handled = true; }
}
private void MDot_Move(object sender, MouseEventArgs e)
{
if (_mDraggingDot == null || _mDraggingGroup == null || _measureOverlay == null) return;
if (e.LeftButton != MouseButtonState.Pressed) return;
var pos = e.GetPosition(_measureOverlay);
SetMeasureDotPos(_mDraggingDot, pos);
if (_mDraggingIsDot1) _mDraggingGroup.P1 = pos; else _mDraggingGroup.P2 = pos;
_mDraggingGroup.UpdateLine();
_mDraggingGroup.UpdateLabel();
RaiseMeasureCompleted(_mDraggingGroup);
e.Handled = true;
}
private void MDot_Up(object sender, MouseButtonEventArgs e)
{
if (_mDraggingDot != null) { _mDraggingDot.ReleaseMouseCapture(); _mDraggingDot = null; _mDraggingGroup = null; e.Handled = true; }
}
private void MDot_RightClick(object sender, MouseButtonEventArgs e)
{
if (sender is not Ellipse dot || _measureOverlay == null) return;
Models.MeasureGroup target = null;
foreach (var g in _measureGroups)
{
if (g.Dot1 == dot || g.Dot2 == dot) { target = g; break; }
}
if (target == null) return;
_measureOverlay.Children.Remove(target.Dot1);
_measureOverlay.Children.Remove(target.Dot2);
_measureOverlay.Children.Remove(target.Line);
_measureOverlay.Children.Remove(target.Label);
_measureGroups.Remove(target);
RaiseMeasureStatusChanged($"已删除测量线 | 剩余 {_measureGroups.Count} 条");
e.Handled = true;
}
// 测量事件
public static readonly RoutedEvent MeasureCompletedEvent =
EventManager.RegisterRoutedEvent(nameof(MeasureCompleted), RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(PolygonRoiCanvas));
public event RoutedEventHandler MeasureCompleted
{
add { AddHandler(MeasureCompletedEvent, value); }
remove { RemoveHandler(MeasureCompletedEvent, value); }
}
private void RaiseMeasureCompleted(Models.MeasureGroup g)
{
RaiseEvent(new MeasureCompletedEventArgs(MeasureCompletedEvent, g.P1, g.P2, g.Distance, _measureGroups.Count));
}
public static readonly RoutedEvent MeasureStatusChangedEvent =
EventManager.RegisterRoutedEvent(nameof(MeasureStatusChanged), RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(PolygonRoiCanvas));
public event RoutedEventHandler MeasureStatusChanged
{
add { AddHandler(MeasureStatusChangedEvent, value); }
remove { RemoveHandler(MeasureStatusChangedEvent, value); }
}
private void RaiseMeasureStatusChanged(string message)
{
RaiseEvent(new MeasureStatusEventArgs(MeasureStatusChangedEvent, message));
}
#endregion Measurement
#region Adorner Management
private void UpdateAdorner()
@@ -387,8 +647,9 @@ namespace XP.ImageProcessing.RoiControl.Controls
if (!isDragging)
{
// 处理点击事件
Point clickPosition = e.GetPosition(mainCanvas);
if (IsMeasuring)
HandleMeasureClick(clickPosition);
OnCanvasClicked(clickPosition);
}
@@ -0,0 +1,43 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace XP.ImageProcessing.RoiControl.Models
{
/// <summary>一次点点距测量的所有视觉元素</summary>
public class MeasureGroup
{
public Ellipse Dot1 { get; set; }
public Ellipse Dot2 { get; set; }
public Line Line { get; set; }
public TextBlock Label { get; set; }
public Point P1 { get; set; }
public Point P2 { get; set; }
public double Distance
{
get
{
double dx = P2.X - P1.X, dy = P2.Y - P1.Y;
return Math.Sqrt(dx * dx + dy * dy);
}
}
public void UpdateLine()
{
Line.X1 = P1.X; Line.Y1 = P1.Y;
Line.X2 = P2.X; Line.Y2 = P2.Y;
Line.Visibility = Visibility.Visible;
}
public void UpdateLabel(string unit = "px")
{
Label.Text = $"{Distance:F2} {unit}";
Canvas.SetLeft(Label, (P1.X + P2.X) / 2 + 8);
Canvas.SetTop(Label, (P1.Y + P2.Y) / 2 - 18);
Label.Visibility = Visibility.Visible;
}
}
}