你的位置:首页 > ASP.net教程

[ASP.net教程]在WPF控件上添加Windows窗口式调整大小行为


起因

项目上需要对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,处理相应的事件实现改变大小。值得注意的是,在左上角、右上角、左下角、上边、左边这些地方实际上不仅是改变大小,同时也会改变控件在宿主中的位置,所以我更愿意称之为调整布局。

主要类及其关系如下:

1

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

2

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

2

可以很明显的看出,在四个角和四条边上各有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 Constructors    public 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