开始之前,先上一张美图。图中的花叫什么,我已经忘了,或者说从来就不知道,总之谓之曰“野花”。只记得花很美,很香,春夏时节,漫山遍野全是她。这大概是七八年前的记忆了,不过她依旧会很准时的在山上沐浴春光,灿烂盛开,只是我看不到罢了。

  文艺过后,就要看到重点了。上图是Windows10自带的图片裁切工具,应该是作为插件集成在“照片”应用中。当然不止于此,几乎所有涉及照片上传类的APP,都会提供裁切图片这个基本功能。实现方式有很多种,我这儿给出自己的一种解决方案。

  先上效果图:

  大致分析如下:

    图片本身不作为裁切工具的一部分,只是把裁切控件放在图片上层,然后调整四个按钮,选出想要裁切的区域,计算出裁切区域的坐标和长宽信息,然后根据比例应用到图片上面,从而实现裁              切。这篇博文主要描述怎么实现裁切控件本身,而实际裁切图片等不进行讨论。

  知道了要干什么,接着就要想想怎么办。

  由于最终要计算出一个裁切区域,所以控件实现一个自定义附加属性,用来对外提供裁切区域信息,为了简单,直接选用Windows.Foundation.Rect这个结构体来描述。

  就该控件自身结构来讲:由Canvas+Path+Button * 4这六个主要的控件来实现。

    Canvas:作为容器,用来承载Path,Button等,关键是方便操作子元素的位置等。

    Button:很明显的四个拖拽点,这儿用Button.Template重写了Button的外观,将其改为一个圆(Ellipse)

        改变中间矩形区域大小就是通过拖拽Button来实现,显然Button支持拖拽,这儿我用自定义Behavior实现它的拖拽功能,关于该Behavior的实现,可以参看上一篇博文《[uwp]自定义Behavior之随意拖动》

    Path:一个填充路径。看到上图中黑色半透明部分,就是该对象的可视部分。具体是通过两个矩形减去重叠区域实现,第一个矩形就是和Canvas等大的一个矩形,第二个矩形就是中间透明区域的矩形,两个矩形进行减去重叠区域的运算后,就可以得到Path的区域。具体的减去操作也很简单,通过GeometryGroup实现,设置其填充规则为FillRule.EvenOdd即可。事实上,经过分解这个Path后,最终就回归到怎么计算中间透明区域大小的问题上,而这个问题,可以通过四个Button的位置来计算。

  通过上面的分析,只需要计算四个Button的位置信息即可,那么这个时候,就可以利用XAML强大的依赖属性系统(DependencyProperty),通过数据绑定等技巧来实际操作。

  针对四个Button的位置信息,分析如下:

  1.四个Button,为了在拖动的任意时刻,保持一个矩形区域,当一个按钮移动时,和他同行或者同列的按钮会跟着动,变化量相同。(此处用左上,右上,左下,右下来标识四个Button)

   所以可以选左上和右下两个Button为主动点,他们的位置定了,另外两个也就定了。值得注意的是,Button的位置是通过附加属性Canvas.Left和Canvas.Top来确定的。所以让左下的Canvas.Left和左上的Canvas.Left绑定,左下的Canvas.Top和右下的Canvas.Top绑定;让右上的Canvas.Left和右下的Canvas.Left绑定,右上Canvas.Top和左上的Canvas.Top绑定。经过绑定之后,左上和右下的位置变化,就能引起左下和右上的位置变化,如果将以上绑定全部设置为双向绑定,那么左下和右上的变化也就同样能引起其他连个主动点的变化。

  2.确定了两个主动点后,便可以自定义一个类来表示这两个主动点的一些信息了(设置坐标X1,Y1,X2,Y2)。在接下来的实现中,用PointModel这个类来表示。  

  

  最终,只需要关注PointModel中两个主动点坐标的变化即可。

  为了检测这种变化,PointModel中定义了四个属性X1,Y1,X2,Y2,在他们的Set方法中,包含了控制矩形大小和主动点自身位置(边界检测和两个Button靠近检测)的一些逻辑。

  接着贴出PointModel的代码:

        public class PointModel : INotifyPropertyChanged{private double _x1;//代表左上Button的Canvas.Leftpublic double X1{get { return _x1; }set{double abspos = 0 - _buttonWidth / 2.0;//button最左可以到达的位置if (value < abspos)//如果实际位置还小于该最小位置,
                    {_x1 = abspos;//则强制修改Button的位置到最边界处_call?.Invoke("X1", _x1);//通知修改Button位置_rectcall?.Invoke();//修改矩形区域位置return;}if ((_x2 - value) >= _minRectWidth)//如果Button和同行的button间距大于_minRectWidth,属正常情况
                    {_x1 = value;OnPropertyChanged();}else//如果小于该最小间距
                    {_x1 = _x2 - _minRectWidth;//根据最小间距,强制修改Button位置。_call?.Invoke("X1", _x1);//通知修改Button位置
                    }_rectcall?.Invoke();//修改矩形区域位置
                }}private double _y1;public double Y1{get { return _y1; }set{double abspos = 0 - _buttonWidth / 2.0;if (value < abspos){_y1 = abspos;_call?.Invoke("Y1", _y1);_rectcall?.Invoke();return;}if ((_y2 - value) >= _minRectWidth){_y1 = value;OnPropertyChanged();}else{_y1 = _y2 - _minRectWidth;_call?.Invoke("Y1", _y1);}_rectcall?.Invoke();}}private double _x2;public double X2{get { return _x2; }set{double abspos = CanvasRect.Width - _buttonWidth / 2.0;if (value > abspos){_x2 = abspos;_call?.Invoke("X2", _x2);_rectcall?.Invoke();return;}if ((value - _x1) >= _minRectWidth){_x2 = value;OnPropertyChanged();}else{_x2 = _minRectWidth + _x1;_call?.Invoke("X2", _x2);}_rectcall?.Invoke();}}private double _y2;public double Y2{get { return _y2; }set{double abspos = CanvasRect.Height - _buttonWidth / 2.0;if (value > abspos){_y2 = abspos;_call?.Invoke("Y2", _y2);_rectcall?.Invoke();return;}if ((value - _y1) >= _minRectWidth){_y2 = value;OnPropertyChanged();}else{_y2 = _y1 + _minRectWidth;_call?.Invoke("Y2", _y2);}_rectcall?.Invoke();}}public event PropertyChangedEventHandler PropertyChanged;public void OnPropertyChanged([CallerMemberName] string propertyName = ""){var handler = PropertyChanged;handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));}/// <summary>/// 用于限制Button靠近边界和互相靠近的回调方法/// </summary>private Action<String, double> _call;/// <summary>/// 用于改变矩形区域大小的回调方法/// </summary>private Action _rectcall;private Rect _canvasRect;//代表中间透明矩形区域public Rect CanvasRect{get { return _canvasRect; }set{_canvasRect = value;OnPropertyChanged();}}private double _buttonWidth; //Button的宽度private double _minRectWidth;//中间透明矩形区域的最小宽度,不能让四个点重合,这儿最小宽度和最小高度都用这个来表示public PointModel(Action<string, double> pointAction, Action rectAction, double btnWidth, double minRectWidth){_call = pointAction;_rectcall = rectAction;_buttonWidth = btnWidth;_minRectWidth = minRectWidth;}}public class RectModel : INotifyPropertyChanged{private GeometryGroup _group;public GeometryGroup Group{get { return _group; }set{_group = value;PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Group"));}}public event PropertyChangedEventHandler PropertyChanged;}    

View Code

  其中最繁杂的部分就是Set方法里面控制Button位置的代码,主要有以下两部分:

    1.保证Button不超出边界

    2.保证Button和其他Button的最小间距。

  在PointModel的构造器中,加入了两个Action,一个SetStaticPoint用来控制Button位置,另一个SetRect用来控制中间透明矩形的大小

    1.针对SetStaticPoint,不同的坐标执行不同的设置方法。

    2.针对SetRect,里面包含了构造Path的方法,如下

        private void SetRect(){if (group == null){group = new GeometryGroup();group.FillRule = FillRule.EvenOdd;//设置规则为减去重叠部分。
            }group.Children.Clear();group.Children.Add(new RectangleGeometry() { Rect = new Rect { X = 0, Y = 0, Height = surface.ActualHeight, Width = surface.ActualWidth } });//大矩形区域,和Canvas同样大小
ClipRect = new Rect { X = Points.X1 + ButtonWidth / 2.0, Y = Points.Y1 + ButtonWidth / 2.0, Width = Points.X2 - Points.X1, Height = Points.Y2 - Points.Y1 };//中间透明区域大小group.Children.Add(new RectangleGeometry() { Rect = ClipRect });RectPath.Group = group;}    

  大致核心如上,其他部分都是细枝末节了。最后我把整个逻辑用UserContrl做了一个简单的整合,弄了一个ClipRectangle控件。

  可以直接作为普通控件使用,只需要设置该控件的Width和Height即可,最后的裁切区域结果,通过一个自定义属性ClipRectProperty来提供。

  

  

  在写这个控件过程中,遇到几个问题和心得如下:

    1.x:Bind:这种绑定方式好像不支持针对附加属性的双向绑定,单向没问题

    2.起初一直尝试直接用Rectangle来实现,但都不行,直到后来用Blend把两个矩形进行相减合并时,发现XAML中把原来的Rectangle换成了Path,于是才有了想法。

    3.DependencyObject是可以说是XAML的核心,这东西一定要学好(现在只是大概会用而已)

  

  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~分割线

  点击这儿下载源码

转载于:https://www.cnblogs.com/cjw1115/p/5323339.html

[uwp]自定义图形裁切控件相关推荐

  1. Android在Activity中动态增加xml自定义样式布局控件(引用xml布局文件和循环增加控件)

    工程目录: MainActivity package com.example.test1121;import androidx.appcompat.app.ActionBar; import andr ...

  2. 龙博方案网Big Faceless Java图形展现控件详细介绍及下载

    2019独角兽企业重金招聘Python工程师标准>>> Graph Library 是用于以Java创建图形和图表的Java类库.它使用全三维模式,可在 PNG.Flash.PDF或 ...

  3. 【WinUI 3】实现自定义标题栏交互控件捕获输入

    问题背景描述 Windows为每个窗口提供默认标题栏,并允许自定义它以匹配应用的个性. 默认标题栏附带一些标准组件和核心功能,例如拖动和调整窗口大小. WinUI 3 中的窗口功能是通过基于 Win3 ...

  4. ImageGear for .NET扫描打印等图形图像处理控件介绍使用手册

    ImageGear for .NET是一款图形图像处理控件,具有扫描,压缩,浏览.添加注释,打印,图像编辑,OCR以及PDF和矢量图像支持,使开发人员可以快速地开发出图像处理程序,可用于.NET Fr ...

  5. Angular19 自定义表单控件

    1 需求 当开发者需要一个特定的表单控件时就需要自己开发一个和默认提供的表单控件用法相似的控件来作为表单控件:自定义的表单控件必须考虑模型和视图之间的数据怎么进行交互 2 官方文档 -> 点击前 ...

  6. C# Winform 通过FlowLayoutPanel及自定义的编辑控件,实现快速构建C/S版的编辑表单页面...

    个人理解,开发应用程序的目的,不论是B/S或是C/S结构类型,无非就是实现可供用户进行查.增.改.删,其中查询用到最多,开发设计的场景也最为复杂,包括但不限于:表格记录查询.报表查询.导出文件查询等等 ...

  7. 自定义用户验证控件CustomValidator

    背景:VisualStudio2005; 使用自定义控件判断注册用户名是否已经存在: 实现: html: <%@ Page Language="C#" AutoEventWi ...

  8. 自定义工作流任务控件

    读moss sdk中的自定义工作流任务控件. 自定义工作流任务控件:任务的创建,修改,删除,完成于一体,同时定义了这四个动作的历史纪录. 自定义时封装属性:  1. 封装任务属性 IsTaskComp ...

  9. [ASP.NET 控件实作 Day28] 图形验证码控件

    在网页上常把图形验证码应用在登入或贴文的页面中,因为图形验证码具有机器不易识别的特性,可以防止机器人程序恶意的存取网页.在本文中将实作一个图形验证码的服务器控件,透过简单的属性设定就可以轻易地在网页上 ...

最新文章

  1. Innodb表压缩过程中遇到的坑(innodb_file_format) - billy鹏
  2. biginteger 原理_Java Bigdecimal使用原理详解
  3. C#实现人脸识别【SqlHelper】
  4. matlab randn state 2,MATLAB?中的randn函数
  5. 修改+oracle+归档模式,修改oracle的归档模式
  6. 吴恩达神经网络和深度学习-学习笔记-45-完全版YOLO算法
  7. [JAVA]全新java初学者实践教程(全)
  8. 系统集成项目管理工程师2022年上半年广东卷下午案例分析题及答案
  9. “站长也疯狂,开车盛宴”——如何选择运维产品
  10. TestReport目录
  11. F28335的ADC模块
  12. 做Gabor滤波器图片
  13. Linux编译DuiLib库报error: no matches converting function ‘ItemComareFunc’ to type ‘__compar_d_fn_t错误解决
  14. 计算机编程的双引号怎么打,计算机双引号怎么打出来
  15. 网络安全第一课--信息收集(一)
  16. java work stealing_工作窃取(work-stealing)算法
  17. 计算机如何与机顶盒连接网络连接网络连接,如何将机顶盒连接到计算机
  18. JWT Token、ID Token、Access Token、Refresh Token
  19. 嵌入式开发之NorFlash 和NandFlash
  20. Web大学生网页作业成品 基于HTML+CSS+JavaScript (刘德华9页 )

热门文章

  1. js网页背景颜色不断变化js特效
  2. 自定义input checkbox样式
  3. 如何让员工心甘情愿地加班
  4. 智能电网如何实现 ?
  5. c语言调声音文件大小,Android MP3文件录制 + 声音分贝大小自定义View实现
  6. html页面出现变量问号,UTF-8编码的html页面显示 (问号)而不是字符
  7. Element-UI框架的使用(二)-- pagination-翻页组件
  8. android 锁频组件,熊猫锁屏组件下载-熊猫锁屏组件 安卓版v1.1.2-PC6安卓网
  9. 数字图像处理实验[实验二:图像拉普拉斯锐化]
  10. 利用zc序列进行简单的帧同步