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), typeof(PolygonRoiCanvas), new PropertyMetadata(null)); public ObservableCollection? ROIItems { get => (ObservableCollection?)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(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(container); } } } } } return null; } private T? FindVisualChild(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(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 } }