点点距测量工具初步实现
This commit is contained in:
@@ -1,12 +1,324 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using XP.ImageProcessing.RoiControl.Controls;
|
||||
using XplorePlane.Events;
|
||||
using XplorePlane.ViewModels;
|
||||
|
||||
namespace XplorePlane.Views
|
||||
{
|
||||
/// <summary>一次测量的所有视觉元素</summary>
|
||||
internal class MeasureGroup
|
||||
{
|
||||
public Ellipse Dot1, Dot2;
|
||||
public Line Line;
|
||||
public TextBlock Label;
|
||||
public Point P1, P2;
|
||||
|
||||
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()
|
||||
{
|
||||
double dx = P2.X - P1.X, dy = P2.Y - P1.Y;
|
||||
double dist = Math.Sqrt(dx * dx + dy * dy);
|
||||
Label.Text = $"{dist:F2} px";
|
||||
Canvas.SetLeft(Label, (P1.X + P2.X) / 2 + 8);
|
||||
Canvas.SetTop(Label, (P1.Y + P2.Y) / 2 - 18);
|
||||
Label.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ViewportPanelView : UserControl
|
||||
{
|
||||
private Canvas _measureOverlay;
|
||||
private readonly List<MeasureGroup> _groups = new();
|
||||
|
||||
// 当前正在创建的测量(只有第一个点,还没完成)
|
||||
private Ellipse _pendingDot;
|
||||
private Point? _pendingPoint;
|
||||
|
||||
// 拖拽状态
|
||||
private Ellipse _draggingDot;
|
||||
private MeasureGroup _draggingGroup;
|
||||
private bool _draggingIsDot1;
|
||||
|
||||
public ViewportPanelView()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContextChanged += OnDataContextChanged;
|
||||
|
||||
RoiCanvas.CanvasClicked += (s, e) =>
|
||||
{
|
||||
if (e is CanvasClickedEventArgs args)
|
||||
OnCanvasClicked(args.Position);
|
||||
};
|
||||
}
|
||||
|
||||
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.OldValue is INotifyPropertyChanged oldVm)
|
||||
oldVm.PropertyChanged -= OnVmPropertyChanged;
|
||||
if (e.NewValue is INotifyPropertyChanged newVm)
|
||||
newVm.PropertyChanged += OnVmPropertyChanged;
|
||||
}
|
||||
|
||||
private void OnVmPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (sender is not ViewportPanelViewModel vm) return;
|
||||
if (e.PropertyName == nameof(ViewportPanelViewModel.CurrentMeasurementMode))
|
||||
{
|
||||
if (vm.CurrentMeasurementMode != MeasurementToolMode.None)
|
||||
Dispatcher.BeginInvoke(new Action(EnsureOverlay),
|
||||
System.Windows.Threading.DispatcherPriority.Loaded);
|
||||
else
|
||||
RemoveOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
#region 覆盖层管理
|
||||
|
||||
private void EnsureOverlay()
|
||||
{
|
||||
if (_measureOverlay != null) return;
|
||||
|
||||
var mainCanvas = FindChildByName<Canvas>(RoiCanvas, "mainCanvas");
|
||||
if (mainCanvas == null) return;
|
||||
|
||||
_measureOverlay = new Canvas
|
||||
{
|
||||
IsHitTestVisible = true,
|
||||
Background = Brushes.Transparent
|
||||
};
|
||||
_measureOverlay.SetBinding(Canvas.WidthProperty,
|
||||
new System.Windows.Data.Binding("CanvasWidth") { Source = RoiCanvas });
|
||||
_measureOverlay.SetBinding(Canvas.HeightProperty,
|
||||
new System.Windows.Data.Binding("CanvasHeight") { Source = RoiCanvas });
|
||||
|
||||
mainCanvas.Children.Add(_measureOverlay);
|
||||
}
|
||||
|
||||
private void RemoveOverlay()
|
||||
{
|
||||
if (_measureOverlay != null)
|
||||
{
|
||||
(VisualTreeHelper.GetParent(_measureOverlay) as Canvas)?.Children.Remove(_measureOverlay);
|
||||
_measureOverlay = null;
|
||||
}
|
||||
_groups.Clear();
|
||||
_pendingDot = null;
|
||||
_pendingPoint = null;
|
||||
_draggingDot = null;
|
||||
_draggingGroup = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 点击处理
|
||||
|
||||
private void OnCanvasClicked(Point pos)
|
||||
{
|
||||
if (DataContext is not ViewportPanelViewModel vm || !vm.IsMeasuring) return;
|
||||
if (vm.CurrentMeasurementMode != MeasurementToolMode.PointDistance) return;
|
||||
if (_measureOverlay == null) EnsureOverlay();
|
||||
if (_measureOverlay == null) return;
|
||||
|
||||
if (!_pendingPoint.HasValue)
|
||||
{
|
||||
// 第一个点
|
||||
_pendingPoint = pos;
|
||||
_pendingDot = CreateDot(Brushes.Red);
|
||||
_measureOverlay.Children.Add(_pendingDot);
|
||||
SetDotPos(_pendingDot, pos);
|
||||
vm.ImageInfo = $"点点距测量 - 第一点: ({pos.X:F0}, {pos.Y:F0}),请点击第二个点";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 第二个点 → 完成一次测量
|
||||
var group = CreateGroup(_pendingPoint.Value, pos);
|
||||
_groups.Add(group);
|
||||
|
||||
// 移除临时的第一个点(已被 group.Dot1 替代)
|
||||
_measureOverlay.Children.Remove(_pendingDot);
|
||||
_pendingDot = null;
|
||||
_pendingPoint = null;
|
||||
|
||||
UpdateStatusBar(vm, group);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 测量组创建
|
||||
|
||||
private MeasureGroup CreateGroup(Point p1, Point p2)
|
||||
{
|
||||
var g = new 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 = CreateDot(Brushes.Red);
|
||||
g.Dot2 = CreateDot(Brushes.Blue);
|
||||
_measureOverlay.Children.Add(g.Dot1);
|
||||
_measureOverlay.Children.Add(g.Dot2);
|
||||
|
||||
SetDotPos(g.Dot1, p1);
|
||||
SetDotPos(g.Dot2, p2);
|
||||
g.UpdateLine();
|
||||
g.UpdateLabel();
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
private Ellipse CreateDot(Brush fill)
|
||||
{
|
||||
var dot = new Ellipse
|
||||
{
|
||||
Width = 12, Height = 12,
|
||||
Fill = fill,
|
||||
Stroke = Brushes.White,
|
||||
StrokeThickness = 1.5,
|
||||
Cursor = Cursors.Hand
|
||||
};
|
||||
dot.MouseLeftButtonDown += Dot_Down;
|
||||
dot.MouseMove += Dot_Move;
|
||||
dot.MouseLeftButtonUp += Dot_Up;
|
||||
dot.MouseRightButtonDown += Dot_RightClick;
|
||||
return dot;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 拖拽
|
||||
|
||||
private void Dot_Down(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is not Ellipse dot) return;
|
||||
|
||||
// 找到这个圆点属于哪个测量组
|
||||
foreach (var g in _groups)
|
||||
{
|
||||
if (g.Dot1 == dot) { _draggingGroup = g; _draggingIsDot1 = true; break; }
|
||||
if (g.Dot2 == dot) { _draggingGroup = g; _draggingIsDot1 = false; break; }
|
||||
}
|
||||
if (_draggingGroup != null)
|
||||
{
|
||||
_draggingDot = dot;
|
||||
dot.CaptureMouse();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Dot_Move(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (_draggingDot == null || _draggingGroup == null || _measureOverlay == null) return;
|
||||
if (e.LeftButton != MouseButtonState.Pressed) return;
|
||||
|
||||
var pos = e.GetPosition(_measureOverlay);
|
||||
SetDotPos(_draggingDot, pos);
|
||||
|
||||
if (_draggingIsDot1) _draggingGroup.P1 = pos;
|
||||
else _draggingGroup.P2 = pos;
|
||||
|
||||
_draggingGroup.UpdateLine();
|
||||
_draggingGroup.UpdateLabel();
|
||||
|
||||
if (DataContext is ViewportPanelViewModel vm)
|
||||
UpdateStatusBar(vm, _draggingGroup);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void Dot_Up(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (_draggingDot != null)
|
||||
{
|
||||
_draggingDot.ReleaseMouseCapture();
|
||||
_draggingDot = null;
|
||||
_draggingGroup = null;
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Dot_RightClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is not Ellipse dot || _measureOverlay == null) return;
|
||||
|
||||
MeasureGroup target = null;
|
||||
foreach (var g in _groups)
|
||||
{
|
||||
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);
|
||||
_groups.Remove(target);
|
||||
|
||||
if (DataContext is ViewportPanelViewModel vm)
|
||||
vm.ImageInfo = $"已删除测量线 | 剩余 {_groups.Count} 条";
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助
|
||||
|
||||
private static void SetDotPos(Ellipse dot, Point pos)
|
||||
{
|
||||
Canvas.SetLeft(dot, pos.X - dot.Width / 2);
|
||||
Canvas.SetTop(dot, pos.Y - dot.Height / 2);
|
||||
}
|
||||
|
||||
private void UpdateStatusBar(ViewportPanelViewModel vm, MeasureGroup g)
|
||||
{
|
||||
double dx = g.P2.X - g.P1.X, dy = g.P2.Y - g.P1.Y;
|
||||
double dist = Math.Sqrt(dx * dx + dy * dy);
|
||||
vm.MeasurementResult = $"{dist:F2} px";
|
||||
vm.ImageInfo = $"点点距: {dist:F2} px | ({g.P1.X:F0},{g.P1.Y:F0}) → ({g.P2.X:F0},{g.P2.Y:F0}) | 共 {_groups.Count} 条测量";
|
||||
}
|
||||
|
||||
private static T FindChildByName<T>(DependencyObject parent, string name) where T : FrameworkElement
|
||||
{
|
||||
int count = VisualTreeHelper.GetChildrenCount(parent);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(parent, i);
|
||||
if (child is T t && t.Name == name) return t;
|
||||
var result = FindChildByName<T>(child, name);
|
||||
if (result != null) return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user