wpf在图上画矩形,矩形可拖动、大小可调节,使用装饰器Adorner调整矩形大小,限制拖动和调节范围

效果
功能

使用wpf实现

  1. 在图片上画一个矩形框
  2. 该矩形框可以调节大小
  3. 该矩形框可以拖动调整位置

注:这里的鼠标事件是,双击在图上画一个固定大小的矩形框,右键按住拖动矩形框。有需要的可以自行调整对应的鼠标事件

参考资料:https://blog.csdn.net/u013113678/article/details/121466724

实现代码

实现自定义的装饰器(可以直接整个复制使用)

csharp 复制代码
public class CanvasAdorner : Adorner
{
    //4条边
    Thumb _leftThumb, _topThumb, _rightThumb, _bottomThumb;
    //4个角
    Thumb _lefTopThumb, _rightTopThumb, _rightBottomThumb, _leftbottomThumb;
    Ellipse _centerPoint;
    private const double thumbSize = 6;
    private const double centerPointRadius = 3;
    Grid _grid;
    UIElement _adornedElement;
    UIElement _parentElement;
    public CanvasAdorner(UIElement adornedElement, UIElement adornedParentElement) : base(adornedElement)
    {
        _adornedElement = adornedElement;
        _parentElement = adornedParentElement;

        // 中心点
        _centerPoint = new Ellipse
        {
            Width = centerPointRadius * 2,
            Height = centerPointRadius * 2,
            Fill = Brushes.Red,
            Stroke = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999999")),
            StrokeThickness = 2,
            HorizontalAlignment = HorizontalAlignment.Center,
            VerticalAlignment = VerticalAlignment.Center,
        };

        //初始化thumb缩放手柄
        _leftThumb = CreateThumb(HorizontalAlignment.Left, VerticalAlignment.Center, Cursors.SizeWE);
        _topThumb = CreateThumb(HorizontalAlignment.Center, VerticalAlignment.Top, Cursors.SizeNS);
        _rightThumb = CreateThumb(HorizontalAlignment.Right, VerticalAlignment.Center, Cursors.SizeWE);
        _bottomThumb = CreateThumb(HorizontalAlignment.Center, VerticalAlignment.Bottom, Cursors.SizeNS);

        _lefTopThumb = CreateThumb(HorizontalAlignment.Left, VerticalAlignment.Top, Cursors.SizeNWSE);
        _rightTopThumb = CreateThumb(HorizontalAlignment.Right, VerticalAlignment.Top, Cursors.SizeNESW);
        _rightBottomThumb = CreateThumb(HorizontalAlignment.Right, VerticalAlignment.Bottom, Cursors.SizeNWSE);
        _leftbottomThumb = CreateThumb(HorizontalAlignment.Left, VerticalAlignment.Bottom, Cursors.SizeNESW);

        _grid = new Grid();
        _grid.Children.Add(_leftThumb);
        _grid.Children.Add(_topThumb);
        _grid.Children.Add(_rightThumb);
        _grid.Children.Add(_bottomThumb);
        _grid.Children.Add(_lefTopThumb);
        _grid.Children.Add(_rightTopThumb);
        _grid.Children.Add(_rightBottomThumb);
        _grid.Children.Add(_leftbottomThumb);
        AddVisualChild(_grid);

        // 绘制中心点和x,y坐标轴
        _grid.Children.Add(_centerPoint);
        DrawAxisWithArrow(0,15,0,0,isXAxis: true);
        DrawAxisWithArrow(0, 0, 10, 25, isXAxis: false);
    }
    protected override Visual GetVisualChild(int index)
    {
        return _grid;
    }
    protected override int VisualChildrenCount
    {
        get
        {
            return 1;
        }
    }
    protected override Size ArrangeOverride(Size finalSize)
    {
        //直接给grid布局,grid内部的thumb会自动布局。
        _grid.Arrange(new Rect(new Point(-_leftThumb.Width / 2, -_leftThumb.Height / 2), new Size(finalSize.Width + _leftThumb.Width, finalSize.Height + _leftThumb.Height)));
        return finalSize;
    }
    
    // 创建缩放手柄
    private Thumb CreateThumb(HorizontalAlignment horizontalAlignment, VerticalAlignment verticalAlignment, Cursor cursor)
    {
        var thumb = new Thumb 
        {
            Width = thumbSize,
            Height = thumbSize,
            HorizontalAlignment = horizontalAlignment,
            VerticalAlignment = verticalAlignment,
            Background = Brushes.Green,
            Cursor = cursor,
            Template = new ControlTemplate(typeof(Thumb))
            {
                VisualTree = GetFactory(new SolidColorBrush(Colors.White))
            },
        };
        thumb.DragDelta += Thumb_DragDelta;
        return thumb;
    }
    // 缩放手柄resize逻辑
    private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
    {
        var c = _adornedElement as FrameworkElement;
        var p = _parentElement as FrameworkElement;
        var thumb = sender as FrameworkElement;
        double left, top, width, height;
        if (thumb.HorizontalAlignment == HorizontalAlignment.Left)
        {
            left = double.IsNaN(Canvas.GetLeft(c)) ? 0 : Canvas.GetLeft(c) + e.HorizontalChange;
            width = c.Width - e.HorizontalChange;
            // 确保不会超出 Canvas 左边界
            if (left < 0)
            {
                width += left; // 减少宽度以适应左侧边界
                left = 0; // 不能再向左移动
            }
        }
        else
        {
            left = Canvas.GetLeft(c);
            width = c.Width + e.HorizontalChange;

            // 确保不会超出 Canvas 右边界
            if (left + width > p.Width)
            {
                width = p.Width - left; // 减少宽度以适应右侧边界
            }
        }
        if (thumb.VerticalAlignment == VerticalAlignment.Top)
        {
            top = double.IsNaN(Canvas.GetTop(c)) ? 0 : Canvas.GetTop(c) + e.VerticalChange;
            height = c.Height - e.VerticalChange;

            // 确保不会超出 Canvas 上边界
            if (top < 0)
            {
                height += top; // 减少高度以适应上侧边界
                top = 0; // 不能再向上移动
            }
        }
        else
        {
            top = Canvas.GetTop(c);
            height = c.Height + e.VerticalChange;

            // 确保不会超出 Canvas 下边界
            if (top + height > p.Height)
            {
                height = p.Height - top; // 减少高度以适应下侧边界
            }
        }
        if (thumb.HorizontalAlignment != HorizontalAlignment.Center)
        {
            if (width >= 0)
            {
                Canvas.SetLeft(c, left);
                c.Width = width;
            }
        }
        if (thumb.VerticalAlignment != VerticalAlignment.Center)
        {
            if (height >= 0)
            {
                Canvas.SetTop(c, top);
                c.Height = height;
            }
        }
    }
    private void DrawAxisWithArrow(int x1, int x2, int y1, int y2, bool isXAxis)
    {
        // 绘制主轴线
        Line axisLine = new Line
        {
            X1 = x1,
            Y1 = y1,
            X2 = x2,
            Y2 = y2,
            Stroke = Brushes.GreenYellow,
            StrokeThickness = 1,
            HorizontalAlignment = HorizontalAlignment.Center,
            VerticalAlignment = VerticalAlignment.Center,
            Margin = new Thickness { Left = x2, Top = 0, Right = 0, Bottom = 0 }
        };
        _grid.Children.Add(axisLine);

        // 绘制箭头
        TextBlock textBlock = new TextBlock 
        { 
            Text=isXAxis?"> x": "∨y",
            Foreground = Brushes.GreenYellow,
            HorizontalAlignment= HorizontalAlignment.Center,
            VerticalAlignment= VerticalAlignment.Center,
            Margin= new Thickness { Left= isXAxis ? x2 * 2:5, Top=y2, Right=0, Bottom= isXAxis ? 2.5:0 }
        };
        _grid.Children.Add(textBlock);
    }
    
    //thumb的样式
    FrameworkElementFactory GetFactory(Brush back)
    {
        var fef = new FrameworkElementFactory(typeof(Ellipse));
        fef.SetValue(Ellipse.FillProperty, back);
        fef.SetValue(Ellipse.StrokeProperty, new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999999")));
        fef.SetValue(Ellipse.StrokeThicknessProperty, (double)2);
        return fef;
    }
}

xaml前台代码(根据自己实际情况调整图片的Source和Canvas的Width、Height,这里我的图片是绑定viewmodel的值,Canvas的宽高是始终跟图片大小一致)

xml 复制代码
<Grid Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center">
    <Image x:Name="imageView" Stretch="Uniform" Source="{Binding LoadTemplate}"
       MouseLeftButtonDown="Image_MouseLeftButtonDown"/>
    <Canvas x:Name="overlayCanvas"
            Width="{Binding ActualWidth, ElementName=imageView}" 
            Height="{Binding ActualHeight, ElementName=imageView}"
         />
</Grid>

xaml后台代码(截取相关代码)

csharp 复制代码
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Documents;
using System.Windows.Input;

private Rectangle _currentRectangle;
private bool _isDragging = false;
private Point _startPoint;
private Point _originalRectanglePosition;
private DateTime _lastClickTime = DateTime.MinValue;

// 图片点击事件
private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    // 双击
    DateTime currentClickTime = DateTime.Now;
    TimeSpan timeSinceLastClick = currentClickTime - _lastClickTime;
    _lastClickTime = currentClickTime;
    if (timeSinceLastClick.TotalMilliseconds >= 300) return;

    if (_currentRectangle != null)
    {
        ResetCanvas();
    }
    Point clickPosition = e.GetPosition(overlayCanvas);
    double rectWidth = 100;
    double rectHeight = 100;
    double rectLeft = clickPosition.X - rectWidth / 2;
    double rectTop = clickPosition.Y - rectHeight / 2;

    // 确保矩形框不会超出 Canvas 的左边界
    if (rectLeft < 0)
    {
        rectLeft = 0;
    }
    // 确保矩形框不会超出 Canvas 的右边界
    if (rectLeft + rectWidth > overlayCanvas.Width)
    {
        rectLeft = overlayCanvas.Width - rectWidth;
    }
    // 确保矩形框不会超出 Canvas 的上边界
    if (rectTop < 0)
    {
        rectTop = 0;
    }
    // 确保矩形框不会超出 Canvas 的下边界
    if (rectTop + rectHeight > overlayCanvas.Height)
    {
        rectTop = overlayCanvas.Height - rectHeight;
    }

    _currentRectangle = new Rectangle
    {
        Width = rectWidth,
        Height = rectHeight,
        Stroke = Brushes.Red,
        Fill = Brushes.Transparent,
        StrokeThickness = 1
    };
    Canvas.SetLeft(_currentRectangle, rectLeft);
    Canvas.SetTop(_currentRectangle, rectTop);
    overlayCanvas.Children.Add(_currentRectangle);

    // 为矩形添加resize装饰器
    var layer = AdornerLayer.GetAdornerLayer(_currentRectangle);
    layer.Add(new CanvasAdorner(_currentRectangle, overlayCanvas));

    // 为矩形添加拖动事件
    _currentRectangle.MouseRightButtonDown += Rectangle_MouseLeftButtonDown;
    _currentRectangle.MouseMove += Rectangle_MouseMove;
    _currentRectangle.MouseRightButtonUp += Rectangle_MouseLeftButtonUp;
    _currentRectangle.MouseEnter += Rectangle_MouseEnter;
}

private void Rectangle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    if (e.RightButton == MouseButtonState.Pressed)
    {
        _isDragging = true;
        _startPoint = e.GetPosition(overlayCanvas);
        _originalRectanglePosition = new Point(Canvas.GetLeft(_currentRectangle), Canvas.GetTop(_currentRectangle));
        _currentRectangle.CaptureMouse();
    }
}
private void Rectangle_MouseMove(object sender, MouseEventArgs e)
{
    if (_isDragging)
    {
        Point currentPosition = e.GetPosition(overlayCanvas);

        // 计算矩形的新位置
        double newLeft = _originalRectanglePosition.X+ (currentPosition.X - _startPoint.X);
        double newTop = _originalRectanglePosition.Y + (currentPosition.Y - _startPoint.Y);

        // 限制矩形不超出 Canvas 边界
        newLeft = Math.Max(0, Math.Min(newLeft, overlayCanvas.Width - _currentRectangle.Width));
        newTop = Math.Max(0, Math.Min(newTop, overlayCanvas.Height - _currentRectangle.Height));

        // 更新矩形的位置
        Canvas.SetLeft(_currentRectangle, newLeft);
        Canvas.SetTop(_currentRectangle, newTop);
    }
}
private void Rectangle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    _isDragging = false;
    _currentRectangle.ReleaseMouseCapture();
}
private void Rectangle_MouseEnter(object sender, MouseEventArgs e)
{
    _currentRectangle.Cursor = Cursors.SizeAll;
}
相关推荐
滚雪球~41 分钟前
npm error code ETIMEDOUT
前端·npm·node.js
沙漏无语43 分钟前
npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
前端·npm·node.js
supermapsupport1 小时前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
brrdg_sefg1 小时前
WEB 漏洞 - 文件包含漏洞深度解析
前端·网络·安全
胡西风_foxww1 小时前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest
m0_748254881 小时前
vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0
前端·vue.js·elementui
可喜~可乐1 小时前
C# WPF开发
microsoft·c#·wpf
星就前端叭2 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
m0_748234522 小时前
前端Vue3字体优化三部曲(webFont、font-spider、spa-font-spider-webpack-plugin)
前端·webpack·node.js
Web阿成2 小时前
3.学习webpack配置 尝试打包ts文件
前端·学习·webpack·typescript