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

news2024/11/24 7:42:09
效果

在这里插入图片描述

功能

使用wpf实现

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

注:这里的鼠标事件是,双击在图上画一个固定大小的矩形框,右键按住拖动矩形框。有需要的可以自行调整对应的鼠标事件
参考资料:https://blog.csdn.net/u013113678/article/details/121466724

实现代码

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

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的宽高是始终跟图片大小一致)

<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后台代码(截取相关代码)

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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2164372.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

心理测试小程序开发心理健康MBTI人格测试,小程序源码部署支持多种流量主

简介 在当今这个高速运转的社会里&#xff0c;个人内心性格与心理健康日益成为人们关注的焦点。随着科技的日新月异&#xff0c;心理评估的方式正经历着深刻的变革&#xff0c;从传统的面对面咨询室中解放出来&#xff0c;无缝融入了我们日常使用的移动设备之中。这一趋势极大…

【网络安全】网络基础第一阶段——第四节:网络协议基础---- VRRP与网络架构设计

目录 一、VRRP 1.1 VRRP使用场景及简介 1.2 VRRP基本原理 1.2.1 VRRP基本结构 1.2.2 设备类型 1.2.3 VRRP工作原理 1.3 VRRP的基本配置 1.3.1 基于三层交换机的VRRP组配置 1.3.2 SMTPVRRP经典组网 1.4 端口聚合 1.4.1 端口聚合技术 1.4.2 聚合模式 1.4.3 Eth-trun…

Python项目Flask框架整合Redis

一、在配置文件中创建Redis连接信息 二、 实现Redis配置类 import redis from config.config import REDIS_HOST, REDIS_PORT, REDIS_PASSWD, REDIS_DB, EXPIRE_TIMEclass RedisDb():def __init__(self, REDIS_HOST, REDIS_PORT, REDIS_DB, EXPIRE_TIME, REDIS_PASSWD):# 建立…

【笔记】KaiOS 系统框架和应用结构(APP界面逻辑)

KaiOS系统框架 最早自下而上分成Gonk-Gecko-Gaia层,代码有同名的目录,现在已经不用这种称呼。 按照官网3.0的版本迭代介绍,2.5->3.0已经将系统更新成如下部分: 仅分为上层web应用和底层平台核心,通过WebAPIs连接上下层,这也是kaios系统升级变更较大的部分。 KaiOS P…

Spring Boot 点餐系统:您的餐饮技术伙伴

摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于网上点餐系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了网上点餐系统&#xff0c;它彻底改变了过去传统的…

Spring不能处理的循环依赖

文章目录 场景一&#xff1a;prototype 类型的循环依赖场景二&#xff1a; constructor 注入的循环依赖场景三&#xff1a;普通的 AOP 代理 Bean 的循环依赖–默认是可以的场景四&#xff1a;Async 增强的 Bean 的循环依赖总结 参考&#xff1a;https://blog.csdn.net/wang4896…

Redis系列补充:聊聊布隆过滤器(go语言实践篇)

1 介绍 布隆过滤器&#xff08;Bloom Filter&#xff09;是 Redis 4.0 版本之后提供的新功能&#xff0c;我们一般将它当做插件加载到 Redis Service服务器中&#xff0c;给 Redis 提供强大的滤重功能。 它是一种概率性数据结构&#xff0c;可用于判断一个元素是否存在于一个集…

Elasticsearch导出导入数据

1.概念回顾 2.基础操作 展示详细信息 GET&#xff1a;http://127.0.0.1:9200/_cat/indices?v Java代码将文件导入到ES package com.Graph.medicalgraph;import org.apache.http.HttpHost; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.act…

Java中的位图和布隆过滤器(如果想知道Java中有关位图和布隆过滤器的知识点,那么只看这一篇就足够了!)

前言&#xff1a;在学习之前的数据结构的时候&#xff0c;我们使用的数据量都不是很大&#xff0c;但是在生活中&#xff0c;我们常常面临着要处理大量数据结果并得出最终结果&#xff0c;那么有没有什么数据结构可以帮助我们实现这样的功能呢&#xff1f; ✨✨✨这里是秋刀鱼不…

Circular dependency between the following tasks(gradle循环依赖的问题)

简介 目前公司项目主要使用gradle而不是maven&#xff0c;所以对gradle的使用不是很清楚&#xff0c;遇到这个问题的时候一直在晚上查资料&#xff0c;但是解决方案基本都是安卓的&#xff0c;后续先前辈请教了一下&#xff0c;才找到解决方案。 问题解析 本质上就是两个模块…

【QT 5 调试软件+Linux下调用脚本shell-无法调度+目录拼写+无法找目录+sudo权限(2)+问题解决方式+后续补充】

【QT 5 调试软件Linux下调用脚本shell-无法调度目录拼写无法找目录sudo权限&#xff08;2&#xff09;问题解决方式后续补充】 1、前言2、问题综述&#xff1a;自研qt上位机无法调度脚本&#xff08;1&#xff09;可能原因1&#xff1a;无法找到目录情况说明&#xff1a;解决思…

程序人生:软件测试 非技术性面试题【建议每个测试人观看】

1、自我介绍&#xff1a;三分钟左右 2、为什么从郑州/太原离职&#xff1f; 3、你的职业规划是什么样的&#xff1f; 4、对下一家公司有什么自己的想法吗&#xff1f; 5、你觉得作为一名测试工程师&#xff0c;应该具备什么样的素养&#xff1f; 6、你觉得管理层&#xff…

HTML | 外部引入 CSS 的2种方式:link和@import有什么区别?

外部引入 CSS 有2种方式&#xff0c;link 和 import。就结论而言&#xff0c;强烈建议使用 link &#xff0c;慎用 import 方式。 两者都是外部引用 CSS 的方式&#xff0c;但是存在一定的区别&#xff1a; &#xff08;1&#xff09;从属关系区别 link是HTML / XHTML标签&a…

react crash course 2024(3) jsx语法及组件

创建组件的文件结构 rafce创建组件内容 const NavBar () > {return (<div>NavBar</div>) }export default NavBar 使用该组件 组件传值 或者把props解构 设置默认值 5.用自定义组件包裹一些元素 把他们传值 元素直接变成了参数被传进来

求推荐网站建设公司?2024网站建设公司哪家好TOP3

好的网站建设公司在这个信息时代寻找起来会比较困难&#xff0c;因为你在任何一个搜索引擎搜索“网站建设”这个关键词&#xff0c;平台会给你推送成千上万家的建站公司&#xff0c;让人看得眼花缭乱&#xff0c;但究竟哪个更好&#xff0c;让人捉摸不透。 前段时间&#xff0…

多城联动、多形式开展网安周公益活动,开源网安传播网络安全知识

9月9日至15日&#xff0c;以“网络安全为人民&#xff0c;网络安全靠人民”为主题的2024年国家网络安全宣传周将在全国范围内统一开展&#xff0c;通过多样的形式、丰富的内容&#xff0c;助力全社会网络安全意识和防护技能提升。开源网安今年继续为各地企业、群众带来了丰富的…

Qt QFileDialog使用方法

头文件 #include <QFileDialog> 成员名称返回值说明getExistingDirectoryQString返回用户选中的文件夹路径getExistingDirectoryUrlQUrl与QFileDialog::getExistingDirectory()的主要区别来自于为用户提供的选择远程目录的能力getOpenFileNameQString返回用户选中的文件…

从更底层的角度理解网站的访问过程

文章目录 1.示例&#xff0c;访问www.baidu.com是如何返回数据的1.输入www.baidu.com回车2.检查本机的C:\Windows\System32\drivers\etc\hosts配置文件夹下有没有这个域名对应的映射&#xff1a; 1.示例&#xff0c;访问www.baidu.com是如何返回数据的 1.输入www.baidu.com回车…

18.2 k8s-apiserver监控源码解读

本节重点介绍 : k8s代码库和模块地址 下载 apiserver源码 apiserver中监控源码阅读 k8s源码地址分布 k8s代码库 访问github上k8s仓库&#xff0c;readme中给出了k8s 模块的代码地址举例图片 组件仓库列表 地址 Repositories currently staged here: k8s.io/apik8s.io/a…

Maven配置、下载教程(非常详细)

maven下载地址 Maven – 发行说明 – Maven 3.6.1 (apache.org) 1.配置settings.xml 下载完maven之后&#xff0c;保存在电脑中并解压 打开maven文件 -->conf-->settings.xml 使用记事本方式打开 打开之后找到这个地方&#xff0c;在电脑中创建一个文件夹(repository)…