zoukankan      html  css  js  c++  java
  • WPF进阶教程

    写下来,备忘。      

      Decorator,有装饰器、装饰品的意思,很容易让人联想到设计模式里面的装饰器模式。Decorator类负责包装某个UI元素,用来提供额外的行为。它有一个类型为UIElement的Child属性,其中含有待包装的内容。Decorator可以被用于添加简单的视觉装饰,比如Border边框,或者更为复杂的行为,比如ViewBox、AdornerDecorator。

           当我们继承Decorator时,也可以自定义添加一些依赖属性,比如Border就定义了BorderBrush,BorderThinckness等用来设置Border的样式。
     
    在想到自定义带三角形的Border之前,我们会想到这么几个问题
    1、边框如何根据里面的内容大小变化而变化
    2、如何才能使三角形和矩形的连接处无缝对接
     
           在之前,我都是使用Grid布局,上面使用BorderThiness为0的Border来包裹文字或者其他空间,下面使用一个三角形Path,这样可以粗略的实现类似效果,但是这个有很大的一个问题就是不能设置BorderThiness,否则三角形Path和Border的连接处会有一根线,无法去除。之外这样写一点都不通用,很傻,但怎么办呢。冥冥中自有天意,无意中看到了Decorator,真是柳暗花明(其实还是自己基础知识不扎实,否则怎么会不知道Decorator)。
     
    好了,在开始自定义控件之前,需要先了解Decorator的一个工作原理。要绘制边框,首先这个边框得先知道我里面包裹的Child元素到底有多大,这就涉及到容器的计算问题。
     
    容器的计算规则
          计算容器永远都是先测量(MeasureOverride),然后通知父元素分配控件,计算好控件后就需要设置子元素的大小与位置(ArrangeOverride),最后准备工作都做好了之后,就要开始绘制了(OnRender)。
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    
    namespace WpfDemo
    {
        public sealed class AngleBorder : Decorator
        {
            public enum EnumPlacement
            {
                /// <summary>
                /// 左上
                /// </summary>
                LeftTop,
                /// <summary>
                /// 左中
                /// </summary>
                LeftBottom,
                /// <summary>
                /// 左下
                /// </summary>
                LeftCenter,
                /// <summary>
                /// 右上
                /// </summary>
                RightTop,
                /// <summary>
                /// 右下
                /// </summary>
                RightBottom,
                /// <summary>
                /// 右中
                /// </summary>
                RightCenter,
                /// <summary>
                /// 上左
                /// </summary>
                TopLeft,
                /// <summary>
                /// 上中
                /// </summary>
                TopCenter,
                /// <summary>
                /// 上右
                /// </summary>
                TopRight,
                /// <summary>
                /// 下左
                /// </summary>
                BottomLeft,
                /// <summary>
                /// 下中
                /// </summary>
                BottomCenter,
                /// <summary>
                /// 下右
                /// </summary>
                BottomRight,
            }
    
            #region 依赖属性
            public static readonly DependencyProperty PlacementProperty =
                DependencyProperty.Register("Placement", typeof(EnumPlacement), typeof(AngleBorder),
                new FrameworkPropertyMetadata(EnumPlacement.RightCenter, FrameworkPropertyMetadataOptions.AffectsRender, OnDirectionPropertyChangedCallback));
    
            public EnumPlacement Placement
            {
                get { return (EnumPlacement)GetValue(PlacementProperty); }
                set { SetValue(PlacementProperty, value); }
            }
    
            public static readonly DependencyProperty TailWidthProperty =
                DependencyProperty.Register("TailWidth", typeof(double), typeof(AngleBorder), new PropertyMetadata(10d));
            /// <summary>
            /// 尾巴的宽度,默认值为7
            /// </summary>
            public double TailWidth
            {
                get { return (double)GetValue(TailWidthProperty); }
                set { SetValue(TailWidthProperty, value); }
            }
            public static readonly DependencyProperty TailHeightProperty =
                DependencyProperty.Register("TailHeight", typeof(double), typeof(AngleBorder), new PropertyMetadata(10d));
            /// <summary>
            /// 尾巴的高度,默认值为10
            /// </summary>
            public double TailHeight
            {
                get { return (double)GetValue(TailHeightProperty); }
                set { SetValue(TailHeightProperty, value); }
            }
    
            public static readonly DependencyProperty TailVerticalOffsetProperty =
                DependencyProperty.Register("TailVerticalOffset", typeof(double), typeof(AngleBorder), new PropertyMetadata(13d));
            /// <summary>
            /// 尾巴距离顶部的距离,默认值为10
            /// </summary>
            public double TailVerticalOffset
            {
                get { return (double)GetValue(TailVerticalOffsetProperty); }
                set { SetValue(TailVerticalOffsetProperty, value); }
            }
            public static readonly DependencyProperty TailHorizontalOffsetProperty =
                DependencyProperty.Register("TailHorizontalOffset", typeof(double), typeof(AngleBorder), 
                    new PropertyMetadata(12d));
            /// <summary>
            /// 尾巴距离顶部的距离,默认值为10
            /// </summary>
            public double TailHorizontalOffset
            {
                get { return (double)GetValue(TailHorizontalOffsetProperty); }
                set { SetValue(TailHorizontalOffsetProperty, value); }
            }
            public static readonly DependencyProperty BackgroundProperty =
                DependencyProperty.Register("Background", typeof(Brush), typeof(AngleBorder)
                    , new PropertyMetadata(new SolidColorBrush(Color.FromRgb(255, 255, 255))));
            /// <summary>
            /// 背景色,默认值为#FFFFFF,白色
            /// </summary>
            public Brush Background
            {
                get { return (Brush)GetValue(BackgroundProperty); }
                set { SetValue(BackgroundProperty, value); }
            }
    
            public static readonly DependencyProperty PaddingProperty =
                DependencyProperty.Register("Padding", typeof(Thickness), typeof(AngleBorder)
                    , new PropertyMetadata(new Thickness(10, 5, 10, 5)));
            /// <summary>
            /// 内边距
            /// </summary>
            public Thickness Padding
            {
                get { return (Thickness)GetValue(PaddingProperty); }
                set { SetValue(PaddingProperty, value); }
            }
    
            public static readonly DependencyProperty BorderBrushProperty =
                DependencyProperty.Register("BorderBrush", typeof(Brush), typeof(AngleBorder)
                    , new PropertyMetadata(default(Brush)));
            /// <summary>
            /// 边框颜色
            /// </summary>
            public Brush BorderBrush
            {
                get { return (Brush)GetValue(BorderBrushProperty); }
                set { SetValue(BorderBrushProperty, value); }
            }
    
            public static readonly DependencyProperty BorderThicknessProperty =
                DependencyProperty.Register("BorderThickness", typeof(Thickness), typeof(AngleBorder), new PropertyMetadata(new Thickness(1d)));
            /// <summary>
            /// 边框大小
            /// </summary>
            public Thickness BorderThickness
            {
                get { return (Thickness)GetValue(BorderThicknessProperty); }
                set { SetValue(BorderThicknessProperty, value); }
            }
    
            public static readonly DependencyProperty CornerRadiusProperty =
                DependencyProperty.Register("CornerRadius", typeof(System.Windows.CornerRadius)
                    , typeof(AngleBorder), new PropertyMetadata(new CornerRadius(0)));
            /// <summary>
            /// 边框大小
            /// </summary>
            public System.Windows.CornerRadius CornerRadius
            {
                get { return (System.Windows.CornerRadius)GetValue(CornerRadiusProperty); }
                set { SetValue(CornerRadiusProperty, value); }
            }
            #endregion
    
            #region 方法重写
            /// <summary>
            /// 该方法用于测量整个控件的大小
            /// </summary>
            /// <param name="constraint"></param>
            /// <returns>控件的大小</returns>
            protected override Size MeasureOverride(Size constraint)
            {
                Thickness padding = this.Padding;
    
                Size result = new Size();
                if (Child != null)
                {
                    //测量子控件的大小
                    Child.Measure(constraint);
    
                    //三角形在左边与右边的,整个容器的宽度则为:里面子控件的宽度 + 设置的padding + 三角形的宽度
                    //三角形在上面与下面的,整个容器的高度则为:里面子控件的高度 + 设置的padding + 三角形的高度
                    switch (Placement)
                    {
                        case EnumPlacement.LeftTop:
                        case EnumPlacement.LeftBottom:
                        case EnumPlacement.LeftCenter:
                        case EnumPlacement.RightTop:
                        case EnumPlacement.RightBottom:
                        case EnumPlacement.RightCenter:
                            result.Width = Child.DesiredSize.Width + padding.Left + padding.Right + this.TailWidth;
                            result.Height = Child.DesiredSize.Height + padding.Top + padding.Bottom;
                            break;
                        case EnumPlacement.TopLeft:
                        case EnumPlacement.TopCenter:
                        case EnumPlacement.TopRight:
                        case EnumPlacement.BottomLeft:
                        case EnumPlacement.BottomCenter:
                        case EnumPlacement.BottomRight:
                            result.Width = Child.DesiredSize.Width + padding.Left + padding.Right;
                            result.Height = Child.DesiredSize.Height + padding.Top + padding.Bottom + this.TailHeight;
                            break;
                        default:
                            break;
                    }
                }
                return result;
            }
    
            /// <summary>
            /// 设置子控件的大小与位置
            /// </summary>
            /// <param name="arrangeSize"></param>
            /// <returns></returns>
            protected override Size ArrangeOverride(Size arrangeSize)
            {
                Thickness padding = this.Padding;
                if (Child != null)
                {
                    switch (Placement)
                    {
                        case EnumPlacement.LeftTop:
                        case EnumPlacement.LeftBottom:
                        case EnumPlacement.LeftCenter:
                            Child.Arrange(new Rect(new Point(padding.Left + this.TailWidth, padding.Top), Child.DesiredSize));
                            //ArrangeChildLeft();
                            break;
                        case EnumPlacement.RightTop:
                        case EnumPlacement.RightBottom:
                        case EnumPlacement.RightCenter:
                            ArrangeChildRight(padding);
                            break;
                        case EnumPlacement.TopLeft:
                        case EnumPlacement.TopRight:
                        case EnumPlacement.TopCenter:
                            Child.Arrange(new Rect(new Point(padding.Left, this.TailHeight + padding.Top), Child.DesiredSize));
                            break;
                        case EnumPlacement.BottomLeft:
                        case EnumPlacement.BottomRight:
                        case EnumPlacement.BottomCenter:
                            Child.Arrange(new Rect(new Point(padding.Left, padding.Top), Child.DesiredSize));
                            break;
                        default:
                            break;
                    }
                }
                return arrangeSize;
            }
    
            private void ArrangeChildRight(Thickness padding)
            {
                double x = padding.Left;
                double y = padding.Top;
    
                if (!Double.IsNaN(this.Height) && this.Height != 0)
                {
                    y = (this.Height - (Child.DesiredSize.Height)) / 2;
                }
    
                Child.Arrange(new Rect(new Point(x, y), Child.DesiredSize));
            }
    
            /// <summary>
            /// 绘制控件
            /// </summary>
            /// <param name="drawingContext"></param>
            protected override void OnRender(DrawingContext drawingContext)
            {
                if (Child != null)
                {
                    Geometry cg = null;
                    Brush brush = null;
                    //DpiScale dpi = base.getd();
                    Pen pen = new Pen();
    
                    pen.Brush = this.BorderBrush;
                    //pen.Thickness = BorderThickness * 0.5;
                    pen.Thickness = AngleBorder.RoundLayoutValue(BorderThickness.Left, DoubleUtil.DpiScaleX);
                    
                    switch (Placement)
                    {
                        case EnumPlacement.LeftTop:
                        case EnumPlacement.LeftBottom:
                        case EnumPlacement.LeftCenter:
                            //生成小尾巴在左侧的图形和底色
                            cg = CreateGeometryTailAtLeft();
                            brush = CreateFillBrush();
                            break;
                        case EnumPlacement.RightTop:
                        case EnumPlacement.RightCenter:
                        case EnumPlacement.RightBottom:
                            //生成小尾巴在右侧的图形和底色
                            cg = CreateGeometryTailAtRight();
                            brush = CreateFillBrush();
                            break;
                        case EnumPlacement.TopLeft:
                        case EnumPlacement.TopCenter:
                        case EnumPlacement.TopRight:
                            //生成小尾巴在右侧的图形和底色
                            cg = CreateGeometryTailAtTop();
                            brush = CreateFillBrush();
                            break;
                        case EnumPlacement.BottomLeft:
                        case EnumPlacement.BottomCenter:
                        case EnumPlacement.BottomRight:
                            //生成小尾巴在右侧的图形和底色
                            cg = CreateGeometryTailAtBottom();
                            brush = CreateFillBrush();
                            break;
                        default:
                            break;
                    }
                    GuidelineSet guideLines = new GuidelineSet();
                    drawingContext.PushGuidelineSet(guideLines);
                    drawingContext.DrawGeometry(brush, pen, cg);
                }
            }
            #endregion
    
            private static double RoundLayoutValue(double value, double dpiScale)
            {
                double num;
                if (!AngleBorder.AreClose(dpiScale, 1.0))
                {
                    num = Math.Round(value * dpiScale) / dpiScale;
                    if (double.IsInfinity(num) || AngleBorder.AreClose(num, 1.7976931348623157E+308))
                    {
                        num = value;
                    }
                }
                else
                {
                    num = Math.Round(value);
                }
                return num;
            }
    
            static bool AreClose(double value1, double value2)
            {
                if (value1 == value2)
                {
                    return true;
                }
                double num = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * 2.2204460492503131E-16;
                double num2 = value1 - value2;
                return -num < num2 && num > num2;
            }
    
            #region 私有方法
            private Geometry CreateGeometryTailAtRight()
            {
                CombinedGeometry result = new CombinedGeometry();
    
                //三角形默认居中
                this.TailVerticalOffset = (this.ActualHeight - this.TailHeight) / 2;
    
                #region 绘制三角形
                Point arcPoint1 = new Point(this.ActualWidth - TailWidth, TailVerticalOffset);
                Point arcPoint2 = new Point(this.ActualWidth, TailVerticalOffset + TailHeight / 2);
                Point arcPoint3 = new Point(this.ActualWidth - TailWidth, TailVerticalOffset + TailHeight);
    
                LineSegment as1_2 = new LineSegment(arcPoint2, false);
                LineSegment as2_3 = new LineSegment(arcPoint3, false);
    
                PathFigure pf1 = new PathFigure();
                pf1.IsClosed = false;
                pf1.StartPoint = arcPoint1;
                pf1.Segments.Add(as1_2);
                pf1.Segments.Add(as2_3);
    
                PathGeometry pg1 = new PathGeometry();
                pg1.Figures.Add(pf1);
                #endregion
    
                #region 绘制矩形边框
                RectangleGeometry rg2 = new RectangleGeometry(new Rect(0, 0, this.ActualWidth - TailWidth, this.ActualHeight)
                    , CornerRadius.TopLeft, CornerRadius.BottomRight, new TranslateTransform(0.5, 0.5));
                #endregion
    
                #region 合并两个图形
                result.Geometry1 = pg1;
                result.Geometry2 = rg2;
                result.GeometryCombineMode = GeometryCombineMode.Union;
                #endregion
    
                return result;
            }
    
            private Geometry CreateGeometryTailAtLeft()
            {
                CombinedGeometry result = new CombinedGeometry();
    
                switch (this.Placement)
                {
                    case EnumPlacement.LeftTop:
                        //不做任何处理
                        break;
                    case EnumPlacement.LeftBottom:
                        this.TailVerticalOffset = this.ActualHeight - this.TailHeight - this.TailVerticalOffset;
                        break;
                    case EnumPlacement.LeftCenter:
                        this.TailVerticalOffset = (this.ActualHeight - this.TailHeight) / 2;
                        break;
                }
    
                #region 绘制三角形
                Point arcPoint1 = new Point(TailWidth, TailVerticalOffset);
                Point arcPoint2 = new Point(0, TailVerticalOffset + TailHeight / 2);
                Point arcPoint3 = new Point(TailWidth, TailVerticalOffset + TailHeight);
    
                LineSegment as1_2 = new LineSegment(arcPoint2, false);
                LineSegment as2_3 = new LineSegment(arcPoint3, false);
    
                PathFigure pf = new PathFigure();
                pf.IsClosed = false;
                pf.StartPoint = arcPoint1;
                pf.Segments.Add(as1_2);
                pf.Segments.Add(as2_3);
    
                PathGeometry g1 = new PathGeometry();
                g1.Figures.Add(pf);
                #endregion
    
                #region 绘制矩形边框
                RectangleGeometry g2 = new RectangleGeometry(new Rect(TailWidth, 0, this.ActualWidth - this.TailWidth, this.ActualHeight)
                    , CornerRadius.TopLeft, CornerRadius.BottomRight);
                #endregion
    
                #region 合并两个图形
                result.Geometry1 = g1;
                result.Geometry2 = g2;
                result.GeometryCombineMode = GeometryCombineMode.Union;
                #endregion
    
                return result;
            }
    
            private Geometry CreateGeometryTailAtTop()
            {
                CombinedGeometry result = new CombinedGeometry();
    
                switch (this.Placement)
                {
                    case EnumPlacement.TopLeft:
                        break;
                    case EnumPlacement.TopCenter:
                        this.TailHorizontalOffset = (this.ActualWidth - this.TailWidth) / 2;
                        break;
                    case EnumPlacement.TopRight:
                        this.TailHorizontalOffset = this.ActualWidth - this.TailWidth - this.TailHorizontalOffset;
                        break;
                }
    
                #region 绘制三角形
                Point anglePoint1 = new Point(this.TailHorizontalOffset, this.TailHeight);
                Point anglePoint2 = new Point(this.TailHorizontalOffset + (this.TailWidth / 2), 0);
                Point anglePoint3 = new Point(this.TailHorizontalOffset + this.TailWidth, this.TailHeight);
    
                LineSegment as1_2 = new LineSegment(anglePoint2, true);
                LineSegment as2_3 = new LineSegment(anglePoint3, true);
    
                PathFigure pf = new PathFigure();
                pf.IsClosed = false;
                pf.StartPoint = anglePoint1;
                pf.Segments.Add(as1_2);
                pf.Segments.Add(as2_3);
    
                PathGeometry g1 = new PathGeometry();
                g1.Figures.Add(pf);
                #endregion
    
                #region 绘制矩形边框
                RectangleGeometry g2 = new RectangleGeometry(new Rect(0, this.TailHeight, this.ActualWidth, this.ActualHeight - this.TailHeight)
                    , CornerRadius.TopLeft, CornerRadius.BottomRight);
                #endregion
    
                #region 合并
                result.Geometry1 = g1;
                result.Geometry2 = g2;
                result.GeometryCombineMode = GeometryCombineMode.Union;
                #endregion
    
                return result;
            }
    
            private Geometry CreateGeometryTailAtBottom()
            {
                CombinedGeometry result = new CombinedGeometry();
    
                switch (this.Placement)
                {
                    case EnumPlacement.BottomLeft:
                        break;
                    case EnumPlacement.BottomCenter:
                        this.TailHorizontalOffset = (this.ActualWidth - this.TailWidth) / 2;
                        break;
                    case EnumPlacement.BottomRight:
                        this.TailHorizontalOffset = this.ActualWidth - this.TailWidth - this.TailHorizontalOffset;
                        break;
                }
                
    
                #region 绘制三角形
                Point anglePoint1 = new Point(this.TailHorizontalOffset, this.ActualHeight - this.TailHeight);
                Point anglePoint2 = new Point(this.TailHorizontalOffset + this.TailWidth / 2, this.ActualHeight);
                Point anglePoint3 = new Point(this.TailHorizontalOffset + this.TailWidth, this.ActualHeight - this.TailHeight);
    
                LineSegment as1_2 = new LineSegment(anglePoint2, true);
                LineSegment as2_3 = new LineSegment(anglePoint3, true);
    
                PathFigure pf = new PathFigure();
                pf.IsClosed = false;
                pf.StartPoint = anglePoint1;
                pf.Segments.Add(as1_2);
                pf.Segments.Add(as2_3);
    
                PathGeometry g1 = new PathGeometry();
                g1.Figures.Add(pf);
                #endregion
    
                #region 绘制矩形边框
                RectangleGeometry g2 = new RectangleGeometry(new Rect(0, 0, this.ActualWidth, this.ActualHeight - this.TailHeight)
                    , CornerRadius.TopLeft, CornerRadius.BottomRight);
                #endregion
    
                #region 合并
                result.Geometry1 = g1;
                result.Geometry2 = g2;
                result.GeometryCombineMode = GeometryCombineMode.Union;
                #endregion
    
                return result;
            }
    
            private Brush CreateFillBrush()
            {
                Brush result = null;
    
                GradientStopCollection gsc = new GradientStopCollection();
                gsc.Add(new GradientStop(((SolidColorBrush)this.Background).Color, 0));
                LinearGradientBrush backGroundBrush = new LinearGradientBrush(gsc, new Point(0, 0), new Point(0, 1));
                result = backGroundBrush;
    
                return result;
            }
    
            /// <summary>
            /// 根据三角形方向设置消息框的水平位置,偏左还是偏右
            /// </summary>
            /// <param name="d"></param>
            /// <param name="e"></param>
            public static void OnDirectionPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var self = d as AngleBorder;
                self.HorizontalAlignment = ((EnumPlacement)e.NewValue == EnumPlacement.RightCenter) ?
                    HorizontalAlignment.Right : HorizontalAlignment.Left;
            }
            #endregion
        }
    }
    

      

    这里面使用了RectangleGeometry与LineSegment,其中使用RectangleGeometry是为了利用其Radius属性可以用来设置圆角,LineSegment则用来绘制三角形的几条直线边,最后利用CombinedGeometry的GeometryCombineMode属性将两个图形进行合并,这样它们连接处就不会有边框存在了,看起来就是一个整体
    result.GeometryCombineMode=GeometryCombineMode.Union;
    

    效果图:

    源码下载:

    链接: https://pan.baidu.com/s/1gfcHLp5 密码: 5b4e
     
  • 相关阅读:
    函数进阶,递归,二分法查找
    内置函数
    IDEA使用maven创建web工程并完善的过程
    后端传入前端的数据的属性名全部为小写的解决方法
    今日总结,复习了很多知识
    org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'ztreeoneServiceImpl': Unsatisfied dependency expressed through field 'baseMapper'; 错误的解决方法
    xxx cannot be resolved to a type 的可能的解决方法,mybatis的Example类不存在
    记录一下Spirng Initializr初始化项目的时候pom文件的内容
    使用nacos进行服务注册的配置
    org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing问题的一种解决方法参考
  • 原文地址:https://www.cnblogs.com/zhidanfeng/p/6576682.html
Copyright © 2011-2022 走看看