规范类名及命名空间名称
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using XP.ImageProcessing.RoiControl.Models;
|
||||
|
||||
namespace XP.ImageProcessing.RoiControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 图像ROI画布控件,支持图像显示、ROI编辑、缩放和平移
|
||||
/// </summary>
|
||||
public class ImageROICanvas : Control
|
||||
{
|
||||
private Canvas? roiCanvas;
|
||||
private bool isDragging = false;
|
||||
private Point mouseDownPoint = new Point();
|
||||
private const double ZoomStep = 1.2;
|
||||
private const double MinZoom = 0.1;
|
||||
private const double MaxZoom = 10.0;
|
||||
|
||||
static ImageROICanvas()
|
||||
{
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageROICanvas),
|
||||
new FrameworkPropertyMetadata(typeof(ImageROICanvas)));
|
||||
}
|
||||
|
||||
public ImageROICanvas()
|
||||
{
|
||||
Loaded += OnLoaded;
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
roiCanvas = GetTemplateChild("PART_Canvas") as Canvas;
|
||||
}
|
||||
|
||||
#region Dependency Properties
|
||||
|
||||
public static readonly DependencyProperty ImageSourceProperty =
|
||||
DependencyProperty.Register(nameof(ImageSource), typeof(ImageSource), typeof(ImageROICanvas),
|
||||
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 = (ImageROICanvas)d;
|
||||
if (e.NewValue is BitmapSource bitmap && control.roiCanvas != null)
|
||||
{
|
||||
control.ImageWidth = bitmap.PixelWidth;
|
||||
control.ImageHeight = bitmap.PixelHeight;
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ROIItemsProperty =
|
||||
DependencyProperty.Register(nameof(ROIItems), typeof(ObservableCollection<ROIShape>), typeof(ImageROICanvas),
|
||||
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(ImageROICanvas),
|
||||
new PropertyMetadata(1.0));
|
||||
|
||||
public double ZoomScale
|
||||
{
|
||||
get => (double)GetValue(ZoomScaleProperty);
|
||||
set => SetValue(ZoomScaleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ZoomCenterProperty =
|
||||
DependencyProperty.Register(nameof(ZoomCenter), typeof(Point), typeof(ImageROICanvas),
|
||||
new PropertyMetadata(new Point()));
|
||||
|
||||
public Point ZoomCenter
|
||||
{
|
||||
get => (Point)GetValue(ZoomCenterProperty);
|
||||
set => SetValue(ZoomCenterProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PanningOffsetXProperty =
|
||||
DependencyProperty.Register(nameof(PanningOffsetX), typeof(double), typeof(ImageROICanvas),
|
||||
new PropertyMetadata(0.0));
|
||||
|
||||
public double PanningOffsetX
|
||||
{
|
||||
get => (double)GetValue(PanningOffsetXProperty);
|
||||
set => SetValue(PanningOffsetXProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PanningOffsetYProperty =
|
||||
DependencyProperty.Register(nameof(PanningOffsetY), typeof(double), typeof(ImageROICanvas),
|
||||
new PropertyMetadata(0.0));
|
||||
|
||||
public double PanningOffsetY
|
||||
{
|
||||
get => (double)GetValue(PanningOffsetYProperty);
|
||||
set => SetValue(PanningOffsetYProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ImageWidthProperty =
|
||||
DependencyProperty.Register(nameof(ImageWidth), typeof(double), typeof(ImageROICanvas),
|
||||
new PropertyMetadata(800.0));
|
||||
|
||||
public double ImageWidth
|
||||
{
|
||||
get => (double)GetValue(ImageWidthProperty);
|
||||
set => SetValue(ImageWidthProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ImageHeightProperty =
|
||||
DependencyProperty.Register(nameof(ImageHeight), typeof(double), typeof(ImageROICanvas),
|
||||
new PropertyMetadata(600.0));
|
||||
|
||||
public double ImageHeight
|
||||
{
|
||||
get => (double)GetValue(ImageHeightProperty);
|
||||
set => SetValue(ImageHeightProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SelectedROIProperty =
|
||||
DependencyProperty.Register(nameof(SelectedROI), typeof(ROIShape), typeof(ImageROICanvas),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
public ROIShape? SelectedROI
|
||||
{
|
||||
get => (ROIShape?)GetValue(SelectedROIProperty);
|
||||
set => SetValue(SelectedROIProperty, value);
|
||||
}
|
||||
|
||||
#endregion Dependency Properties
|
||||
|
||||
#region Mouse Events
|
||||
|
||||
protected override void OnMouseWheel(MouseWheelEventArgs e)
|
||||
{
|
||||
base.OnMouseWheel(e);
|
||||
|
||||
Point mousePos = e.GetPosition(this);
|
||||
|
||||
if (e.Delta > 0)
|
||||
{
|
||||
ZoomIn(mousePos);
|
||||
}
|
||||
else
|
||||
{
|
||||
ZoomOut(mousePos);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseLeftButtonDown(e);
|
||||
mouseDownPoint = e.GetPosition(this);
|
||||
isDragging = false;
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseMove(e);
|
||||
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
Point mousePoint = e.GetPosition(this);
|
||||
double mouseMoveLength = (mousePoint - mouseDownPoint).Length;
|
||||
|
||||
if (mouseMoveLength > 10 / ZoomScale)
|
||||
{
|
||||
isDragging = true;
|
||||
PanningOffsetX += mousePoint.X - mouseDownPoint.X;
|
||||
PanningOffsetY += mousePoint.Y - mouseDownPoint.Y;
|
||||
mouseDownPoint = mousePoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseLeftButtonUp(e);
|
||||
|
||||
if (!isDragging)
|
||||
{
|
||||
// 处理点击事件(如添加多边形顶点)
|
||||
Point clickPoint = e.GetPosition(roiCanvas);
|
||||
OnCanvasClicked(clickPoint);
|
||||
}
|
||||
|
||||
isDragging = false;
|
||||
}
|
||||
|
||||
#endregion Mouse Events
|
||||
|
||||
#region Zoom Methods
|
||||
|
||||
public void ZoomIn(Point center)
|
||||
{
|
||||
double newZoom = ZoomScale * ZoomStep;
|
||||
if (newZoom <= MaxZoom)
|
||||
{
|
||||
ZoomCenter = center;
|
||||
ZoomScale = newZoom;
|
||||
}
|
||||
}
|
||||
|
||||
public void ZoomOut(Point center)
|
||||
{
|
||||
double newZoom = ZoomScale / ZoomStep;
|
||||
if (newZoom >= MinZoom)
|
||||
{
|
||||
ZoomCenter = center;
|
||||
ZoomScale = newZoom;
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetZoom()
|
||||
{
|
||||
ZoomScale = 1.0;
|
||||
PanningOffsetX = 0;
|
||||
PanningOffsetY = 0;
|
||||
ZoomCenter = new Point();
|
||||
}
|
||||
|
||||
#endregion Zoom Methods
|
||||
|
||||
#region Events
|
||||
|
||||
public static readonly RoutedEvent CanvasClickedEvent =
|
||||
EventManager.RegisterRoutedEvent(nameof(CanvasClicked), RoutingStrategy.Bubble,
|
||||
typeof(RoutedEventHandler), typeof(ImageROICanvas));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#endregion Events
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 画布点击事件参数
|
||||
/// </summary>
|
||||
public class CanvasClickedEventArgs : RoutedEventArgs
|
||||
{
|
||||
public Point Position { get; }
|
||||
|
||||
public CanvasClickedEventArgs(RoutedEvent routedEvent, Point position) : base(routedEvent)
|
||||
{
|
||||
Position = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<UserControl x:Class="XP.ImageProcessing.RoiControl.Controls.PolygonRoiCanvas"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:XP.ImageProcessing.RoiControl.Controls"
|
||||
xmlns:models="clr-namespace:XP.ImageProcessing.RoiControl.Models"
|
||||
xmlns:converters="clr-namespace:XP.ImageProcessing.RoiControl.Converters"
|
||||
xmlns:behaviors="clr-namespace:XP.ImageProcessing.RoiControl"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
x:Name="root"
|
||||
Background="White">
|
||||
<UserControl.Resources>
|
||||
<behaviors:PointListToPointCollectionConverter x:Key="PointListToPointCollectionConverter" />
|
||||
<converters:ROITypeToVisibilityConverter x:Key="ROITypeToVisibilityConverter" />
|
||||
</UserControl.Resources>
|
||||
<Border BorderBrush="Transparent" BorderThickness="1" ClipToBounds="True">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 左侧控制按钮 -->
|
||||
<Border Grid.Column="0" Background="White" Padding="5">
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Top">
|
||||
<Button x:Name="btnZoomIn" Content="+" Background="White" BorderBrush="LightGray" Width="40" Height="40" Margin="2" Click="BtnZoomIn_Click" />
|
||||
<Button x:Name="btnZoomOut" Content="-" Background="White" BorderBrush="LightGray" Width="40" Height="40" Margin="2" Click="BtnZoomOut_Click" />
|
||||
<Button x:Name="btnReset" Content="适应" Background="White" BorderBrush="LightGray" Width="40" Height="40" Margin="2" Click="BtnReset_Click" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 图像显示区域 -->
|
||||
<Grid Grid.Column="1" x:Name="imageDisplayGrid" ClipToBounds="True">
|
||||
<Grid x:Name="transformGrid"
|
||||
RenderTransformOrigin="0,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform x:Name="scaleTransform"
|
||||
ScaleX="{Binding ZoomScale, ElementName=root}"
|
||||
ScaleY="{Binding ZoomScale, ElementName=root}" />
|
||||
<TranslateTransform x:Name="translateTransform"
|
||||
X="{Binding PanOffsetX, ElementName=root}"
|
||||
Y="{Binding PanOffsetY, ElementName=root}" />
|
||||
</TransformGroup>
|
||||
</Grid.RenderTransform>
|
||||
|
||||
<Canvas x:Name="mainCanvas"
|
||||
Width="{Binding CanvasWidth, ElementName=root}"
|
||||
Height="{Binding CanvasHeight, ElementName=root}"
|
||||
Background="Transparent"
|
||||
MouseWheel="Canvas_MouseWheel"
|
||||
MouseLeftButtonDown="Canvas_MouseLeftButtonDown"
|
||||
MouseLeftButtonUp="Canvas_MouseLeftButtonUp"
|
||||
MouseMove="Canvas_MouseMove"
|
||||
MouseRightButtonDown="Canvas_MouseRightButtonDown">
|
||||
|
||||
<!-- 背景图像 -->
|
||||
<Image x:Name="backgroundImage"
|
||||
Source="{Binding ImageSource, ElementName=root}"
|
||||
Width="{Binding CanvasWidth, ElementName=root}"
|
||||
Height="{Binding CanvasHeight, ElementName=root}"
|
||||
Stretch="Fill" />
|
||||
|
||||
<!-- ROI显示 - 只支持多边形 -->
|
||||
<ItemsControl ItemsSource="{Binding ROIItems, ElementName=root}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<!-- 多边形ROI -->
|
||||
<Polygon x:Name="polygonShape"
|
||||
behaviors:PolygonPointsBehavior.PointsSource="{Binding Points}"
|
||||
Stroke="{Binding Color}"
|
||||
StrokeThickness="1"
|
||||
Fill="Transparent"
|
||||
MouseLeftButtonDown="ROI_MouseLeftButtonDown" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Canvas>
|
||||
</Grid>
|
||||
|
||||
<!-- 缩放比例显示 -->
|
||||
<TextBlock Text="{Binding ZoomScale, ElementName=root, StringFormat=Zoom Scale: {0:P0}}"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Margin="10"
|
||||
Padding="5"
|
||||
Background="#AA000000"
|
||||
Foreground="White"
|
||||
FontSize="10" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,527 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user