起因

项目上需要对Canvas中的控件添加调整大小功能,即能在控件的四个角和四条边上可进行相应的拖动,类似Windows窗口那种。于是在参考以前同事写的代码基础上,完成了该功能。

代码实现

Adorner

我们是给现有的控件添加功能,属于装饰功能。当然首先想到的就是Adorner。在MSDN中Adorner的介绍如下:

装饰器是一个绑定到 UIElement 的自定义 FrameworkElement。 装饰器呈现在装饰器层中,它是一个呈现图面,始终位于装饰元素或装饰元素集合的顶部;呈现装饰器独立于呈现该装饰器绑定到的 UIElement。 装饰器通常相对于其绑定到的元素进行定位,且使用位于装饰元素的左上部的标准 2-D 坐标原点进行定位。

关于Adorner更详细的信息,可参考WPF - Adorner - loveis715 - 博客园。Adorner是一个抽象类,我们可以继承自该类来实现自己的装饰功能。

Thumb

WPF中存在支持拖动的Thumb控件,而且Thumb控件继承自Control,可以定义控件模板。Thumb最重要的三个事件如下:

Thumb 提供 DragStarted, DragCompleted 和 DragDelta 事件来管理与鼠标指针相关的拖动操作。 当用户按下鼠标左键时,Thumb 控件接收逻辑焦点和鼠标捕获,并引发 DragStarted 事件。 在 Thumb 控件具有焦点和鼠标捕获的同时,可以无限制地多次引发 DragDelta 事件。 当用户释放鼠标左键时,Thumb 控件失去鼠标捕获,并引发 DragCompleted 事件。

实现原理

思路很明确,就是自定义一个Adorner,在四条边和四个角上添加相应的Thumb,处理相应的事件实现改变大小。值得注意的是,在左上角、右上角、左下角、上边、左边这些地方实际上不仅是改变大小,同时也会改变控件在宿主中的位置,所以我更愿意称之为调整布局。

主要类及其关系如下:

添加CanvasArrangementAdorner之后控件效果如下(浅蓝色为控件):

因为将Thumb设为透明了,看不出来是由8个Thumb组成的,如果改下颜色,会更容易理解些。

可以很明显的看出,在四个角和四条边上各有4个Thumb,我重新定义了Thumb的控件模板,控件模板内部是一个Rectangle。

主要类

各个主要类如下,因代码较简单,就不多解释了。

ArrangementDirection

using System;/// <summary>
/// 布局方向
/// </summary>
[Flags]
public enum ArrangementDirection
{None = 0,LeftTop = 1,Top = 2,RightTop = 4,Right = 8,RightBottom = 16,Bottom = 32,LeftBottom = 64,Left = 128,All = LeftTop | Top | RightTop | Right | RightBottom | Bottom | LeftBottom | Left,
}

ArrangementChangedEventArgs

using System;
using System.Windows;/// <summary>
/// 布局变化的事件
/// </summary>
public class ArrangementChangedEventArgs : EventArgs
{public ArrangementChangedEventArgs(Rect oldArrangement, Rect newArrangement){this.OldArrangement = oldArrangement;this.NewArrangement = newArrangement;}/// <summary>/// 旧布局信息/// </summary>public Rect OldArrangement { get; private set; }/// <summary>/// 新布局信息/// </summary>public Rect NewArrangement { get; private set; }
}

ArrangementDirection

using System;/// <summary>
/// 布局方向
/// </summary>
[Flags]
public enum ArrangementDirection
{None = 0,LeftTop = 1,Top = 2,RightTop = 4,Right = 8,RightBottom = 16,Bottom = 32,LeftBottom = 64,Left = 128,All = LeftTop | Top | RightTop | Right | RightBottom | Bottom | LeftBottom | Left,
}

ArrangementAdorner

using System;using System.Diagnostics.Contracts;using System.Windows;using System.Windows.Controls;using System.Windows.Controls.Primitives;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Shapes;/// <summary>/// 布局装饰器/// </summary>public abstract class ArrangementAdorner : Adorner{#region Fields/// <summary>/// 拖动方块的边长/// </summary>private const double ThumbSideLength = 6;/// <summary>/// 可视化对象集合/// </summary>private readonly VisualCollection visualCollection;/// <summary>/// 对齐方向/// </summary>private readonly ArrangementDirection direction;/// <summary>/// 各个方向的拖动方块/// </summary>private readonly Thumb topThumb,leftTopthumb,rightTopThumb,righThumb,rightBottomThumb,bottomThumb,leftBottomThumb,leftThumb;/// <summary>/// 当前位置/// </summary>private Point currentLocation;/// <summary>/// 拖动前的大小/// </summary>private Size oldSize;/// <summary>/// 拖动前左边缘的值/// </summary>private double oldLeft;/// <summary>/// 拖动前上边缘的值/// </summary>private double oldTop;#endregion Fields#region Constructors/// <summary>/// 构造函数/// </summary>/// <param name="adornedElement">装饰器所要绑定到的元素。</param>/// <param name="arrangementDirection">布局方向</param>protected ArrangementAdorner(FrameworkElement adornedElement, ArrangementDirection arrangementDirection = ArrangementDirection.All): base(adornedElement){this.direction = arrangementDirection;this.visualCollection = new VisualCollection(this);this.AddThumbIfNeeded(ref this.leftTopthumb,ArrangementDirection.LeftTop,HorizontalAlignment.Left,VerticalAlignment.Top,Cursors.SizeNWSE);this.AddThumbIfNeeded(ref this.topThumb,ArrangementDirection.Top,HorizontalAlignment.Stretch,VerticalAlignment.Top,Cursors.SizeNS);this.AddThumbIfNeeded(ref this.rightTopThumb,ArrangementDirection.RightTop,HorizontalAlignment.Right,VerticalAlignment.Top,Cursors.SizeNESW);this.AddThumbIfNeeded(ref this.righThumb,ArrangementDirection.Right,HorizontalAlignment.Right,VerticalAlignment.Stretch,Cursors.SizeWE);this.AddThumbIfNeeded(ref this.rightBottomThumb,ArrangementDirection.RightBottom,HorizontalAlignment.Right,VerticalAlignment.Bottom,Cursors.SizeNWSE);this.AddThumbIfNeeded(ref this.bottomThumb,ArrangementDirection.Bottom,HorizontalAlignment.Stretch,VerticalAlignment.Bottom,Cursors.SizeNS);this.AddThumbIfNeeded(ref this.leftBottomThumb,ArrangementDirection.LeftBottom,HorizontalAlignment.Left,VerticalAlignment.Bottom,Cursors.SizeNESW);this.AddThumbIfNeeded(ref this.leftThumb,ArrangementDirection.Left,HorizontalAlignment.Left,VerticalAlignment.Stretch,Cursors.SizeWE);}#endregion Constructorspublic event EventHandler<ArrangementChangedEventArgs> ArrangementChanged;#region Protected Methods#region Overrides/// <summary>/// 获取此元素内的可视化子元素的数目。/// </summary>/// <returns>/// 此元素内的可视化子元素的数目。/// </returns>protected override int VisualChildrenCount{get{return this.visualCollection.Count;}}/// <summary>/// 定位子元素并确定大小。/// </summary>/// <returns>/// 所用的实际大小。/// </returns>/// <param name="finalSize">排列自身及其子元素的最终区域。</param>protected override Size ArrangeOverride(Size finalSize){this.ArrangeThumbIfNeeded(this.leftTopthumb,new Point(-ThumbSideLength, -ThumbSideLength),new Size(ThumbSideLength, ThumbSideLength));this.ArrangeThumbIfNeeded(this.topThumb,new Point(0, -ThumbSideLength),new Size(finalSize.Width, ThumbSideLength));this.ArrangeThumbIfNeeded(this.rightTopThumb,new Point(finalSize.Width, -ThumbSideLength),new Size(ThumbSideLength, ThumbSideLength));this.ArrangeThumbIfNeeded(this.righThumb,new Point(finalSize.Width, 0),new Size(ThumbSideLength, finalSize.Height));this.ArrangeThumbIfNeeded(this.rightBottomThumb,new Point(finalSize.Width, finalSize.Height),new Size(ThumbSideLength, ThumbSideLength));this.ArrangeThumbIfNeeded(this.bottomThumb,new Point(0, finalSize.Height),new Size(finalSize.Width, ThumbSideLength));this.ArrangeThumbIfNeeded(this.leftBottomThumb,new Point(-ThumbSideLength, finalSize.Height),new Size(ThumbSideLength, ThumbSideLength));this.ArrangeThumbIfNeeded(this.leftThumb,new Point(-ThumbSideLength, 0),new Size(ThumbSideLength, finalSize.Height));return base.ArrangeOverride(finalSize);}/// <summary>/// 从子元素集合返回指定索引处的子级。/// </summary>/// <returns>/// 所请求的子元素。它不应返回 null;如果提供的索引超出范围,将引发异常。/// </returns>/// <param name="index">集合中所请求子元素从零开始的索引。</param>protected override Visual GetVisualChild(int index){return this.visualCollection[index];}#endregion Overrides#region Virtuals/// <summary>/// 创建布局方块/// </summary>/// <param name="horizontalAlignment">方块的水平对齐方向</param>/// <param name="verticalAlignment">方块的垂直对齐方向</param>/// <param name="cursor">方块的光标</param>/// <returns>创建好的方块</returns>protected virtual Thumb CreateResizeThumb(HorizontalAlignment horizontalAlignment,VerticalAlignment verticalAlignment,Cursor cursor){var thumb = new Thumb{HorizontalAlignment = horizontalAlignment,VerticalAlignment = verticalAlignment,Cursor = cursor,Template = this.GetResizeThumbControlTemplate()};return thumb;}/// <summary>/// 获取框架元素的位置/// </summary>/// <param name="element">框架元素</param>/// <returns>框架元素所在的位置</returns>protected abstract Point GetLocation(FrameworkElement element);/// <summary>/// 判断框架元素的位置偏移是否合法/// </summary>/// <param name="element">框架元素</param>/// <param name="offset">偏移向量</param>/// <returns>合法返回true,否则返回false</returns>protected virtual bool IsLocationOffsetLegal(FrameworkElement element, Vector offset){var targetLocation = this.currentLocation + offset;if (targetLocation.X < 0){return false;}return true;}/// <summary>/// 设置框架元素的位置/// </summary>/// <param name="element">框架元素</param>/// <param name="location">新位置</param>protected abstract void SetLocation(FrameworkElement element, Point location);/// <summary>/// 获取框架元素的宽度/// </summary>/// <param name="element">框架元素</param>/// <returns>宽度</returns>protected virtual double GetWidth(FrameworkElement element){return element.Width;}/// <summary>/// 获取框架元素的高度/// </summary>/// <param name="element">框架元素</param>/// <returns>高度</returns>protected virtual double GetHeight(FrameworkElement element){return element.Height;}/// <summary>/// 判断框架元素的宽度变化是否合法/// </summary>/// <param name="element">框架元素</param>/// <param name="widthDelta">宽度变化</param>/// <returns>变化是否合法</returns>protected virtual bool IsWidthDeltaLegal(FrameworkElement element, double widthDelta){double newWidth = this.GetWidth(element) + widthDelta;return this.IsInRange(element.MaxWidth, element.MinWidth, newWidth);}/// <summary>/// 判断框架元素的高度变化是否合法/// </summary>/// <param name="element">框架元素</param>/// <param name="heightDelta">高度变化</param>/// <returns>变化是否合法</returns>protected virtual bool IsHeightDeltaLegal(FrameworkElement element, double heightDelta){double newHeight = this.GetHeight(element) + heightDelta;return this.IsInRange(element.MaxHeight, element.MinHeight, newHeight);}/// <summary>/// 设置框架元素的宽度变化/// </summary>/// <param name="element">框架元素</param>/// <param name="widthDelta">宽度变化</param>protected virtual void SetWidthDelta(FrameworkElement element, double widthDelta){element.Width += widthDelta;}/// <summary>/// 设置框架元素的高度变化/// </summary>/// <param name="element">框架元素</param>/// <param name="heightDelta">高度变化</param>protected virtual void SetHeightDelta(FrameworkElement element, double heightDelta){element.Height += heightDelta;}#endregion Virtuals#endregion  Protected Methods#region Private Methods/// <summary>/// 在需要时添加拖动方块/// </summary>/// <param name="thumb">类中对应的方块</param>/// <param name="arrangementDirection">方块对应的布局方向</param>/// <param name="horizontalAlignment">方块的水平对齐方向</param>/// <param name="verticalAlignment">方块的垂直对齐方向</param>/// <param name="cursor">方块的光标</param>private void AddThumbIfNeeded(ref Thumb thumb,ArrangementDirection arrangementDirection,HorizontalAlignment horizontalAlignment,VerticalAlignment verticalAlignment,Cursor cursor){if (this.HasDirectionFlagSet(arrangementDirection)){thumb = this.CreateResizeThumb(horizontalAlignment, verticalAlignment, cursor);thumb.DragStarted += this.ThumbDragStarted;thumb.DragDelta += this.ThumbDragDelta;thumb.DragCompleted += this.ThumbDragCompleted;this.visualCollection.Add(thumb);}}/// <summary>/// 判断布局方向是否被设置/// </summary>/// <param name="arrangementDirection">布局方向</param>/// <returns>被设置返回true,否则返回false</returns>private bool HasDirectionFlagSet(ArrangementDirection arrangementDirection){return (this.direction & arrangementDirection) == arrangementDirection;}/// <summary>/// 获取布局方块的控件模板/// </summary>/// <returns>控件模板</returns>private ControlTemplate GetResizeThumbControlTemplate(){var factory = new FrameworkElementFactory(typeof(Rectangle));factory.SetValue(Shape.FillProperty, Brushes.Transparent);factory.SetValue(Shape.StrokeProperty, Brushes.Transparent);var controlTemplate = new ControlTemplate { TargetType = typeof(Thumb), VisualTree = factory };return controlTemplate;}/// <summary>/// 在需要时定位并确定方块大小/// </summary>/// <param name="thumb">方块</param>/// <param name="location">方块位置</param>/// <param name="size">方块大小</param>private void ArrangeThumbIfNeeded(Thumb thumb, Point location, Size size){if (thumb != null){if (thumb.HorizontalAlignment != HorizontalAlignment.Stretch){thumb.Width = size.Width;}if (thumb.VerticalAlignment != VerticalAlignment.Stretch){thumb.Height = size.Height;}thumb.Arrange(new Rect(location, size));}}/// <summary>/// 判断一个值是否在范围中/// </summary>/// <param name="maximum">最大值,可取</param>/// <param name="minimum">最小值,可取</param>/// <param name="value">值</param>/// <returns>值在范围中返回true,否则返回false</returns>private bool IsInRange(double maximum, double minimum, double value){return (value >= minimum) && (value <= maximum);}#endregion Private Methods#region Events Handler/// <summary>/// 拖动开始的响应/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void ThumbDragStarted(object sender, DragStartedEventArgs e){var frameworkElement = this.AdornedElement as FrameworkElement;this.currentLocation = this.GetLocation(frameworkElement);this.oldLeft = this.currentLocation.X;this.oldTop = this.currentLocation.Y;var width = this.GetWidth(frameworkElement);var height = this.GetHeight(frameworkElement);this.oldSize = new Size(width, height);}/// <summary>/// 拖动变化的响应/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void ThumbDragDelta(object sender, DragDeltaEventArgs e){var frameworkElement = this.AdornedElement as FrameworkElement;var thumb = sender as Thumb;Contract.Assert(thumb != null);switch (thumb.HorizontalAlignment){case HorizontalAlignment.Left:{var offset = new Vector(e.HorizontalChange, 0);if (this.IsLocationOffsetLegal(frameworkElement, offset)){this.currentLocation.Offset(e.HorizontalChange, 0);if (this.IsWidthDeltaLegal(frameworkElement, -e.HorizontalChange)){this.SetWidthDelta(frameworkElement, -e.HorizontalChange);}}break;}case HorizontalAlignment.Right:{if (this.IsWidthDeltaLegal(frameworkElement, e.HorizontalChange)){this.SetWidthDelta(frameworkElement, e.HorizontalChange);}break;}}switch (thumb.VerticalAlignment){case VerticalAlignment.Top:{var offset = new Vector(0, e.VerticalChange);if (this.IsLocationOffsetLegal(frameworkElement, offset)){this.currentLocation.Offset(0, e.VerticalChange);if (this.IsHeightDeltaLegal(frameworkElement, -e.VerticalChange)){this.SetHeightDelta(frameworkElement, -e.VerticalChange);}}break;}case VerticalAlignment.Bottom:{if (this.IsHeightDeltaLegal(frameworkElement, e.VerticalChange)){this.SetHeightDelta(frameworkElement, e.VerticalChange);}break;}}this.SetLocation(frameworkElement, this.currentLocation);}/// <summary>/// 拖动结束的响应/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void ThumbDragCompleted(object sender, DragCompletedEventArgs e){if (this.ArrangementChanged != null){var frameworkElement = this.AdornedElement as FrameworkElement;var oldArrangement = new Rect(new Point(this.oldLeft, this.oldTop), this.oldSize);var newArrangement = new Rect(this.GetLocation(frameworkElement),new Size(this.GetWidth(frameworkElement), this.GetHeight(frameworkElement)));this.ArrangementChanged(this, new ArrangementChangedEventArgs(oldArrangement, newArrangement));}}#endregion Events Handler}

CanvasArrangementAdorner

using System.Windows;using System.Windows.Controls;/// <summary>/// 画布布局装饰器/// </summary>public class CanvasArrangementAdorner : ArrangementAdorner{/// <summary>/// 构造函数/// </summary>/// <param name="adornedElement">装饰器所要绑定到的元素。</param>/// <param name="arrangementDirection">布局方向</param>public CanvasArrangementAdorner(FrameworkElement adornedElement, ArrangementDirection arrangementDirection = ArrangementDirection.All): base(adornedElement, arrangementDirection){}#region Overrides of ArrangementAdorner/// <summary>/// 获取框架元素的位置/// </summary>/// <param name="element">框架元素</param>/// <returns>框架元素所在的位置</returns>protected override Point GetLocation(FrameworkElement element){return new Point(Canvas.GetLeft(element), Canvas.GetTop(element));}/// <summary>/// 设置框架元素的位置/// </summary>/// <param name="element">框架元素</param>/// <param name="location">新位置</param>protected override void SetLocation(FrameworkElement element, Point location){Canvas.SetLeft(element, location.X);Canvas.SetTop(element, location.Y);}#endregion}

代码下载

博客园:ControlResize

转载于:https://www.cnblogs.com/yiyan127/p/WPF-ControlResize.html

在WPF控件上添加Windows窗口式调整大小行为相关推荐

  1. Android 点击事件,如何在界面上或者某个控件上添加点击事件

    说到点击事件,第一印象 setOnClickListener() 方法,这也是用的最多的控件点击事件方法,这篇文章不分享这个方法,使用该方法的前提是你要拿到这个控件的 View 才可以调用这个方法 通 ...

  2. TextView控件上添加表情图片

    自己弄了个自定义控件继承自TextView package org.face; import android.content.Context; import android.graphics.draw ...

  3. C#如何在panl控件上添加Form窗体

    1. if (treeView1.SelectedNode.Text == "个人信息"){Form1 f4 = new Form1();f4.TopLevel = false;p ...

  4. qt获取窗口的右上角位置_如何获得 Qt窗口部件在主窗口中的位置--确定鼠标是否在某一控件上与在控件上的位置...

    用Qt Creator 设计程序时,最方便的就是ui设计器,可以很容易的得到想要的布局. 但是这样自动布局带来的后果是很难知道窗口中某一部件在主窗口中的相对位置. 在处理子窗口鼠标事件时变的很麻烦.主 ...

  5. qt整个窗口上绘制矩形与在窗口的子控件上绘制矩形

    引言 创建一个基于QDialog的项目,自动生成ui文件,此时再添加新文件,创建一个基于QWidget的ui类,此类具有自定义标题栏,让该自定义类显示在窗口Dialog上,一开始具有红色的边框线,且四 ...

  6. WPF中ContextMenu(右键菜单)使用Command在部分控件上默认为灰色的处理方法

    WPF中ContextMenu(右键菜单)使用Command在部分控件上默认为灰色的处理方法 原文:WPF中ContextMenu(右键菜单)使用Command在部分控件上默认为灰色的处理方法 问题描 ...

  7. MFC/VC++中怎样将一个位图添加到数据库中并且将其读出来到指定的Picture控件上

    看这篇文章之前建议先看一下下面这篇文章: VC下显示位图的几种方法   http://blog.chinaunix.net/uid-607545-id-2088102.html或  http://bl ...

  8. Json解析后添加到ListView控件上

    主要实现的功能:就是将Jsonj解析后,显示在ListV iew控件上(包括网络取图片,是否显示劵.卡,团) Json {"resultCode":1,"resultIn ...

  9. WPF开发人员必读:WPF控件测试台

    介绍 WpfControlTestbench帮助您为您的控件或您想要调查其行为的任何控件编写快速复杂的测试窗口.只需十几行XAML即可创建以下Window内容: 它在左下角显示你要测试的控件,在Win ...

  10. 开源WPF控件库-AdonisUI

    原文:https://github.com/benruehl/adonis-ui 翻译:沙漠尽头的狼(谷歌翻译加持) 用于 WPF 应用程序的轻量级 UI 工具包,提供经典和增强的 Windows 视 ...

最新文章

  1. 基于先验LiDAR点云地图的单目VIO定位(IROS 2020)
  2. 截取字符串slice(),substring() ,substr()。
  3. c语言利用文件体写在桌面上,在C语言中怎样新建一个文件夹?
  4. react-state状态
  5. C语言的printf一些骚操作
  6. 医学数字成像设备中计算机系统的作用包括,医学影像实用技术教程全部习题答案...
  7. python找到两个有序列表的中位数
  8. [Python图像处理] .获取图像属性、兴趣ROI区域及通道处理
  9. android网易云音乐api调用,网易云音乐常用API浅析 – Moonlib
  10. APP(Android)性能测试实例(查询FPS/内存/CPU)【Appetizer+PerfDog】
  11. 用强化学习制作游戏AI
  12. bat文件改成sh文件在linux上运行java程序
  13. MyBatis实现中间表关联查询
  14. 【洛谷P3386】【模板】二分图匹配【网络流】
  15. edge浏览器 F12中文换成英文
  16. 谷歌大胃王,欲占领所有渠道--谷歌电视
  17. RabbitMQ-dlx死信队列
  18. 2023中国程序员薪酬报告出炉,你拖后腿了吗?
  19. 《计算机图形学编程(使用OpenGL和C++)》
  20. 【操作系统】面向真题学操作系统 —— 操作系统概述客观题

热门文章

  1. LINUX SHELL脚本多行注释
  2. 建议让游客参与修长城
  3. SHELL中如何对一个变量进行算术操作(加减)
  4. 为枪击事件默哀,程序员们确实要重视代码规范
  5. 计算机应用水平excel考什么,全国专业技术人员计算机应用能力考试EXCEL2003中文字处理全真模拟试卷(三)...
  6. VC++如何获取目标程序的句柄hProcess
  7. 同台加载_跨年官宣 | “爷青回”我只服湖南卫视跨年 李易峰陈伟霆马天宇“古剑三侠”同台...
  8. idea shell 使用linux_Linux 基础操作
  9. Java http响应状态码_如何获得HttpClient返回状态码和响应正文?
  10. C语言 - 数组作为参数传递给函数(按值传递和按引用传递)