zoukankan      html  css  js  c++  java
  • 在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

  • 相关阅读:
    只有程序员才懂这些黑色幽默!
    只有程序员才懂这些黑色幽默!
    程序员常访问的国外技术交流网站
    回归分析:非线性nlinfi
    Java设计模式(二十一):职责链模式
    Angular4——7.表单处理
    ubuntu 代理设置
    Qt 隐藏标题栏 窗口移动 鼠标事件
    Shevon's Blog
    Allenmind's Blog
  • 原文地址:https://www.cnblogs.com/yiyan127/p/WPF-ControlResize.html
Copyright © 2011-2022 走看看