527 lines
18 KiB
C#
527 lines
18 KiB
C#
using System;
|
|
using System.Collections.ObjectModel;
|
|
using System.Collections.Specialized;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Documents;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using System.Windows.Shapes;
|
|
using XP.ImageProcessing.RoiControl.Models;
|
|
|
|
namespace XP.ImageProcessing.RoiControl.Controls
|
|
{
|
|
public partial class PolygonRoiCanvas : UserControl
|
|
{
|
|
private bool isDragging = false;
|
|
private Point lastMousePosition;
|
|
private const double ZoomStep = 1.2;
|
|
private Adorner? currentAdorner;
|
|
|
|
public PolygonRoiCanvas()
|
|
{
|
|
InitializeComponent();
|
|
Loaded += PolygonRoiCanvas_Loaded;
|
|
}
|
|
|
|
private void PolygonRoiCanvas_Loaded(object sender, RoutedEventArgs e)
|
|
{
|
|
// 监听ROI集合变化
|
|
if (ROIItems != null)
|
|
{
|
|
ROIItems.CollectionChanged += ROIItems_CollectionChanged;
|
|
foreach (var roi in ROIItems)
|
|
{
|
|
roi.PropertyChanged += ROI_PropertyChanged;
|
|
|
|
// 如果是多边形ROI,监听Points集合变化
|
|
if (roi is PolygonROI polygonROI)
|
|
{
|
|
polygonROI.Points.CollectionChanged += Points_CollectionChanged;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ROIItems_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
|
{
|
|
if (e.NewItems != null)
|
|
{
|
|
foreach (ROIShape roi in e.NewItems)
|
|
{
|
|
roi.PropertyChanged += ROI_PropertyChanged;
|
|
|
|
// 如果是多边形ROI,监听Points集合变化
|
|
if (roi is PolygonROI polygonROI)
|
|
{
|
|
polygonROI.Points.CollectionChanged += Points_CollectionChanged;
|
|
}
|
|
}
|
|
}
|
|
if (e.OldItems != null)
|
|
{
|
|
foreach (ROIShape roi in e.OldItems)
|
|
{
|
|
roi.PropertyChanged -= ROI_PropertyChanged;
|
|
|
|
// 取消监听Points集合变化
|
|
if (roi is PolygonROI polygonROI)
|
|
{
|
|
polygonROI.Points.CollectionChanged -= Points_CollectionChanged;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Points_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
|
{
|
|
// 只在删除或添加顶点时更新Adorner,拖拽时的Replace操作不触发更�?
|
|
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove ||
|
|
e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
|
|
{
|
|
// Points集合变化时,如果当前选中的是多边形ROI,更新Adorner
|
|
if (SelectedROI is PolygonROI polygonROI && sender == polygonROI.Points)
|
|
{
|
|
// 使用Dispatcher延迟更新,确保UI已经处理完Points的变�?
|
|
Dispatcher.BeginInvoke(new Action(() =>
|
|
{
|
|
UpdateAdorner();
|
|
}), System.Windows.Threading.DispatcherPriority.Render);
|
|
}
|
|
}
|
|
// Replace操作(拖拽时)不需要重建Adorner,只需要让现有Adorner重新布局
|
|
}
|
|
|
|
private void ROI_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == nameof(ROIShape.IsSelected))
|
|
{
|
|
UpdateAdorner();
|
|
}
|
|
// 监听Points属性变化(整个集合替换的情况)
|
|
else if (e.PropertyName == "Points" && sender is PolygonROI)
|
|
{
|
|
UpdateAdorner();
|
|
}
|
|
}
|
|
|
|
#region Dependency Properties
|
|
|
|
public static readonly DependencyProperty ImageSourceProperty =
|
|
DependencyProperty.Register(nameof(ImageSource), typeof(ImageSource), typeof(PolygonRoiCanvas),
|
|
new PropertyMetadata(null, OnImageSourceChanged));
|
|
|
|
public ImageSource? ImageSource
|
|
{
|
|
get => (ImageSource?)GetValue(ImageSourceProperty);
|
|
set => SetValue(ImageSourceProperty, value);
|
|
}
|
|
|
|
private static void OnImageSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
var control = (PolygonRoiCanvas)d;
|
|
if (e.NewValue is ImageSource imageSource)
|
|
{
|
|
control.CanvasWidth = imageSource.Width;
|
|
control.CanvasHeight = imageSource.Height;
|
|
}
|
|
}
|
|
|
|
public static readonly DependencyProperty ROIItemsProperty =
|
|
DependencyProperty.Register(nameof(ROIItems), typeof(ObservableCollection<ROIShape>), typeof(PolygonRoiCanvas),
|
|
new PropertyMetadata(null));
|
|
|
|
public ObservableCollection<ROIShape>? ROIItems
|
|
{
|
|
get => (ObservableCollection<ROIShape>?)GetValue(ROIItemsProperty);
|
|
set => SetValue(ROIItemsProperty, value);
|
|
}
|
|
|
|
public static readonly DependencyProperty ZoomScaleProperty =
|
|
DependencyProperty.Register(nameof(ZoomScale), typeof(double), typeof(PolygonRoiCanvas),
|
|
new PropertyMetadata(1.0, OnZoomScaleChanged));
|
|
|
|
public double ZoomScale
|
|
{
|
|
get => (double)GetValue(ZoomScaleProperty);
|
|
set => SetValue(ZoomScaleProperty, Math.Max(0.1, Math.Min(10.0, value)));
|
|
}
|
|
|
|
private static void OnZoomScaleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
var control = (PolygonRoiCanvas)d;
|
|
// 缩放变化时更新Adorner以调整控制点大小
|
|
control.UpdateAdorner();
|
|
}
|
|
|
|
public static readonly DependencyProperty PanOffsetXProperty =
|
|
DependencyProperty.Register(nameof(PanOffsetX), typeof(double), typeof(PolygonRoiCanvas),
|
|
new PropertyMetadata(0.0, OnPanOffsetChanged));
|
|
|
|
public double PanOffsetX
|
|
{
|
|
get => (double)GetValue(PanOffsetXProperty);
|
|
set => SetValue(PanOffsetXProperty, value);
|
|
}
|
|
|
|
public static readonly DependencyProperty PanOffsetYProperty =
|
|
DependencyProperty.Register(nameof(PanOffsetY), typeof(double), typeof(PolygonRoiCanvas),
|
|
new PropertyMetadata(0.0, OnPanOffsetChanged));
|
|
|
|
public double PanOffsetY
|
|
{
|
|
get => (double)GetValue(PanOffsetYProperty);
|
|
set => SetValue(PanOffsetYProperty, value);
|
|
}
|
|
|
|
private static void OnPanOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
var control = (PolygonRoiCanvas)d;
|
|
|
|
// 平移时重建Adorner,确保控制点位置正确
|
|
if (control.SelectedROI != null && control.SelectedROI.IsSelected)
|
|
{
|
|
control.UpdateAdorner();
|
|
}
|
|
}
|
|
|
|
public static readonly DependencyProperty CanvasWidthProperty =
|
|
DependencyProperty.Register(nameof(CanvasWidth), typeof(double), typeof(PolygonRoiCanvas),
|
|
new PropertyMetadata(800.0));
|
|
|
|
public double CanvasWidth
|
|
{
|
|
get => (double)GetValue(CanvasWidthProperty);
|
|
set => SetValue(CanvasWidthProperty, value);
|
|
}
|
|
|
|
public static readonly DependencyProperty CanvasHeightProperty =
|
|
DependencyProperty.Register(nameof(CanvasHeight), typeof(double), typeof(PolygonRoiCanvas),
|
|
new PropertyMetadata(600.0));
|
|
|
|
public double CanvasHeight
|
|
{
|
|
get => (double)GetValue(CanvasHeightProperty);
|
|
set => SetValue(CanvasHeightProperty, value);
|
|
}
|
|
|
|
public static readonly DependencyProperty SelectedROIProperty =
|
|
DependencyProperty.Register(nameof(SelectedROI), typeof(ROIShape), typeof(PolygonRoiCanvas),
|
|
new PropertyMetadata(null, OnSelectedROIChanged));
|
|
|
|
public ROIShape? SelectedROI
|
|
{
|
|
get => (ROIShape?)GetValue(SelectedROIProperty);
|
|
set => SetValue(SelectedROIProperty, value);
|
|
}
|
|
|
|
private static void OnSelectedROIChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
var control = (PolygonRoiCanvas)d;
|
|
|
|
// 更新IsSelected状�?
|
|
if (e.OldValue is ROIShape oldROI)
|
|
{
|
|
oldROI.IsSelected = false;
|
|
}
|
|
if (e.NewValue is ROIShape newROI)
|
|
{
|
|
newROI.IsSelected = true;
|
|
}
|
|
|
|
control.UpdateAdorner();
|
|
}
|
|
|
|
#endregion Dependency Properties
|
|
|
|
#region Adorner Management
|
|
|
|
private void UpdateAdorner()
|
|
{
|
|
// 移除旧的Adorner
|
|
if (currentAdorner != null)
|
|
{
|
|
var adornerLayer = AdornerLayer.GetAdornerLayer(mainCanvas);
|
|
if (adornerLayer != null)
|
|
{
|
|
adornerLayer.Remove(currentAdorner);
|
|
}
|
|
currentAdorner = null;
|
|
}
|
|
|
|
// 为选中的ROI添加Adorner
|
|
if (SelectedROI != null && SelectedROI.IsSelected)
|
|
{
|
|
// 查找对应的UI元素
|
|
var container = FindROIVisual(SelectedROI);
|
|
if (container != null)
|
|
{
|
|
var adornerLayer = AdornerLayer.GetAdornerLayer(mainCanvas);
|
|
if (adornerLayer != null)
|
|
{
|
|
double scaleFactor = 1.0 / ZoomScale;
|
|
|
|
if (SelectedROI is PolygonROI polygonROI)
|
|
{
|
|
currentAdorner = new PolygonAdorner(container, scaleFactor, polygonROI);
|
|
}
|
|
|
|
if (currentAdorner != null)
|
|
{
|
|
adornerLayer.Add(currentAdorner);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private UIElement? FindROIVisual(ROIShape roi)
|
|
{
|
|
// 在ItemsControl中查找对应的视觉元素
|
|
var itemsControl = FindVisualChild<ItemsControl>(mainCanvas);
|
|
if (itemsControl != null)
|
|
{
|
|
for (int i = 0; i < itemsControl.Items.Count; i++)
|
|
{
|
|
if (itemsControl.Items[i] == roi)
|
|
{
|
|
// 尝试获取容器
|
|
var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as ContentPresenter;
|
|
|
|
// 如果容器还没生成,尝试强制生�?
|
|
if (container == null)
|
|
{
|
|
// 强制生成容器
|
|
itemsControl.UpdateLayout();
|
|
container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as ContentPresenter;
|
|
}
|
|
|
|
if (container != null)
|
|
{
|
|
// 查找实际的形状元素(只支持多边形�?
|
|
if (roi is PolygonROI)
|
|
{
|
|
return FindVisualChild<Polygon>(container);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private T? FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
|
|
{
|
|
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
|
|
{
|
|
var child = VisualTreeHelper.GetChild(parent, i);
|
|
if (child is T result)
|
|
{
|
|
return result;
|
|
}
|
|
var childOfChild = FindVisualChild<T>(child);
|
|
if (childOfChild != null)
|
|
{
|
|
return childOfChild;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
#endregion Adorner Management
|
|
|
|
#region Mouse Events
|
|
|
|
private void Canvas_MouseWheel(object sender, MouseWheelEventArgs e)
|
|
{
|
|
// 获取鼠标�?imageDisplayGrid 中的位置
|
|
Point mousePos = e.GetPosition(imageDisplayGrid);
|
|
|
|
// 获取鼠标�?Canvas 中的位置(缩放前�?
|
|
Point mousePosOnCanvas = e.GetPosition(mainCanvas);
|
|
|
|
double oldZoom = ZoomScale;
|
|
double newZoom = oldZoom;
|
|
|
|
if (e.Delta > 0)
|
|
{
|
|
newZoom = oldZoom * ZoomStep;
|
|
}
|
|
else
|
|
{
|
|
newZoom = oldZoom / ZoomStep;
|
|
}
|
|
|
|
// 限制缩放范围
|
|
newZoom = Math.Max(0.1, Math.Min(10.0, newZoom));
|
|
|
|
if (Math.Abs(newZoom - oldZoom) > 0.001)
|
|
{
|
|
// 计算缩放比例变化
|
|
double scale = newZoom / oldZoom;
|
|
|
|
// 更新缩放
|
|
ZoomScale = newZoom;
|
|
|
|
// 调整平移偏移,使鼠标位置保持不变
|
|
// 新的偏移 = 旧偏�?+ 鼠标位置 - 鼠标位置 * 缩放比例
|
|
PanOffsetX = mousePos.X - (mousePos.X - PanOffsetX) * scale;
|
|
PanOffsetY = mousePos.Y - (mousePos.Y - PanOffsetY) * scale;
|
|
}
|
|
|
|
e.Handled = true;
|
|
}
|
|
|
|
private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
lastMousePosition = e.GetPosition(imageDisplayGrid);
|
|
isDragging = false;
|
|
mainCanvas.CaptureMouse();
|
|
}
|
|
|
|
private void Canvas_MouseMove(object sender, MouseEventArgs e)
|
|
{
|
|
if (e.LeftButton == MouseButtonState.Pressed && mainCanvas.IsMouseCaptured)
|
|
{
|
|
Point currentPosition = e.GetPosition(imageDisplayGrid);
|
|
Vector delta = currentPosition - lastMousePosition;
|
|
|
|
if (delta.Length > 5)
|
|
{
|
|
isDragging = true;
|
|
PanOffsetX += delta.X;
|
|
PanOffsetY += delta.Y;
|
|
lastMousePosition = currentPosition;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
|
{
|
|
mainCanvas.ReleaseMouseCapture();
|
|
|
|
if (!isDragging)
|
|
{
|
|
// 处理点击事件
|
|
Point clickPosition = e.GetPosition(mainCanvas);
|
|
OnCanvasClicked(clickPosition);
|
|
}
|
|
|
|
isDragging = false;
|
|
}
|
|
|
|
private void Canvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
// 右键点击完成多边�?
|
|
OnRightClick();
|
|
e.Handled = true;
|
|
}
|
|
|
|
private void ROI_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
// 选择ROI
|
|
if (sender is FrameworkElement element && element.DataContext is ROIShape roi)
|
|
{
|
|
SelectedROI = roi;
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
#endregion Mouse Events
|
|
|
|
#region Public Methods
|
|
|
|
public void ResetView()
|
|
{
|
|
// 自动适应显示窗口 (类似 PictureBox SizeMode.Zoom)
|
|
ZoomScale = 1.0;
|
|
PanOffsetX = 0;
|
|
PanOffsetY = 0;
|
|
|
|
if (imageDisplayGrid != null && CanvasWidth > 0 && CanvasHeight > 0)
|
|
{
|
|
// 使用 Dispatcher 延迟执行,确保布局已完�?
|
|
Dispatcher.BeginInvoke(new Action(() =>
|
|
{
|
|
// 获取图像显示区域的实际尺�?
|
|
double viewportWidth = imageDisplayGrid.ActualWidth;
|
|
double viewportHeight = imageDisplayGrid.ActualHeight;
|
|
|
|
if (viewportWidth > 0 && viewportHeight > 0)
|
|
{
|
|
// 计算宽度和高度的缩放比例
|
|
double scaleX = viewportWidth / CanvasWidth;
|
|
double scaleY = viewportHeight / CanvasHeight;
|
|
|
|
// 选择较小的缩放比例,确保图像完全显示在窗口内(保持宽高比�?
|
|
ZoomScale = Math.Min(scaleX, scaleY);
|
|
|
|
// 居中显示�?Grid �?HorizontalAlignment �?VerticalAlignment 自动处理
|
|
PanOffsetX = 0;
|
|
PanOffsetY = 0;
|
|
}
|
|
}), System.Windows.Threading.DispatcherPriority.Loaded);
|
|
}
|
|
}
|
|
|
|
private void BtnZoomIn_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
double newZoom = ZoomScale * 1.2;
|
|
if (newZoom <= 10.0)
|
|
{
|
|
ZoomScale = newZoom;
|
|
}
|
|
}
|
|
|
|
private void BtnZoomOut_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
double newZoom = ZoomScale / 1.2;
|
|
if (newZoom >= 0.1)
|
|
{
|
|
ZoomScale = newZoom;
|
|
}
|
|
}
|
|
|
|
private void BtnReset_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
ResetView();
|
|
}
|
|
|
|
#endregion Public Methods
|
|
|
|
#region Events
|
|
|
|
public static readonly RoutedEvent CanvasClickedEvent =
|
|
EventManager.RegisterRoutedEvent(nameof(CanvasClicked), RoutingStrategy.Bubble,
|
|
typeof(RoutedEventHandler), typeof(PolygonRoiCanvas));
|
|
|
|
public event RoutedEventHandler CanvasClicked
|
|
{
|
|
add { AddHandler(CanvasClickedEvent, value); }
|
|
remove { RemoveHandler(CanvasClickedEvent, value); }
|
|
}
|
|
|
|
protected virtual void OnCanvasClicked(Point position)
|
|
{
|
|
var args = new CanvasClickedEventArgs(CanvasClickedEvent, position);
|
|
RaiseEvent(args);
|
|
}
|
|
|
|
public static readonly RoutedEvent RightClickEvent =
|
|
EventManager.RegisterRoutedEvent(nameof(RightClick), RoutingStrategy.Bubble,
|
|
typeof(RoutedEventHandler), typeof(PolygonRoiCanvas));
|
|
|
|
public event RoutedEventHandler RightClick
|
|
{
|
|
add { AddHandler(RightClickEvent, value); }
|
|
remove { RemoveHandler(RightClickEvent, value); }
|
|
}
|
|
|
|
protected virtual void OnRightClick()
|
|
{
|
|
RaiseEvent(new RoutedEventArgs(RightClickEvent));
|
|
}
|
|
|
|
#endregion Events
|
|
}
|
|
} |