【问题标题】:WPF animation/UI features performance and benchmarkingWPF 动画/UI 功能性能和基准测试
【发布时间】:2009-06-07 13:20:00
【问题描述】:

我正在为一些业务线的一些花哨的 WPF UI 工作做一个相对较小的概念验证。甚至不用太疯狂,当我使用许多我认为是首先考虑将 WPF 用于 UI 构建的主要原因时,我已经看到了一些非常糟糕的性能。我在这里问了一个问题,为什么我的动画在第一次运行时就停止了,最后我发现一个非常简单的 UserControl 只需要半秒钟来构建它的可视化树。我能够解决该症状,但是初始化一个简单控件需要很长时间这一事实确实让我感到困扰。现在,我正在测试有无 DropShadowEffect 的动画,结果是白天黑夜。一个微妙的阴影让我的控件看起来好多了,但它完全破坏了动画的平滑度。让我也不要从字体渲染开始。当控件有一堆渐变画笔和一个阴影时,我的动画计算使文本模糊了大约整整一秒钟,然后慢慢变成焦点。

所以,我想我的问题是,是否有已知的研究、博客文章或文章详细说明当前 WPF 版本中哪些功能对业务关键型应用程序存在危害。效果(即 DropShadowEffect)、渐变画笔、关键帧动画等是否会对渲染质量(或者这些东西的组合)产生过多的负面影响? WPF 4.0 的最终版本会纠正其中的一些问题吗?我已经读到 VS2010 beta 有一些同样的问题,并且它们应该在最终版本中得到解决。这是因为 WPF 本身的改进,还是因为一半的应用程序将使用以前的技术重建?

【问题讨论】:

    标签: .net wpf performance


    【解决方案1】:

    我还在研究与 WPF 中的 DropShadow 效果相关的性能问题。

    基本上,经验法则总是“不要使用它”。 WPF 中的 PixelShader 和位图 DropShadow 效果都不适用于真实世界的应用程序...除非您的 UI 保持 100% 静态且没有移动部件,否则您的程序将像垃圾一样在 95% 的客户群中运行。

    我要做的是假装它。

    这是我刚刚想出的一个快速的“ndirty DropShadow Decorator”,它可以作为 PixelShader 投影效果的合理复制品。您将希望通过缓存画笔等使其更清洁,但这应该会给您一个想法:

    using System.Windows.Shapes;
    using System.Windows.Controls;
    using System.Windows;
    using System.Windows.Media;
    using System;
    using System.ComponentModel; 
    
    namespace SichboPVR
    {
        /// <summary>
        /// Emulates the System.Windows.Media.Effects.DropShadowEffect using
        /// rectangles and gradients, which performs a million times better
        /// and won't randomly crash a good percentage of your end-user's 
        /// video drivers.
        /// </summary>
        class FastShadow : Decorator 
        {
    
            #region Dynamic Properties
    
            public static readonly DependencyProperty ColorProperty =
                    DependencyProperty.Register(
                            "Color",
                            typeof(Color),
                            typeof(FastShadow),
                            new FrameworkPropertyMetadata(
                                    Color.FromArgb(0x71, 0x00, 0x00, 0x00),
                                    FrameworkPropertyMetadataOptions.AffectsRender));
    
            /// <summary>
            /// The Color property defines the Color used to fill the shadow region. 
            /// </summary> 
            [Category("Common Properties")]
            public Color Color
            {
                get { return (Color)GetValue(ColorProperty); }
                set { SetValue(ColorProperty, value); }
            }
    
            /// <summary>
            /// Distance from centre, why MS don't call this "distance" beats
            /// me.. Kept same as other Effects for consistency.
            /// </summary>
            [Category("Common Properties"), Description("Distance from centre")]
            public double ShadowDepth
            {
                get { return (double)GetValue(ShadowDepthProperty); }
                set { SetValue(ShadowDepthProperty, value); }
            }
    
            // Using a DependencyProperty as the backing store for ShadowDepth.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty ShadowDepthProperty =
                DependencyProperty.Register("ShadowDepth", typeof(double), typeof(FastShadow), 
                new FrameworkPropertyMetadata(
                    5.0, FrameworkPropertyMetadataOptions.AffectsRender,
                        new PropertyChangedCallback((o, e) => {
                            FastShadow f = o as FastShadow;
                            if ((double)e.NewValue < 0)
                                f.ShadowDepth = 0;
                        })));
    
    
            /// <summary>
            /// Size of the shadow
            /// </summary>
            [Category("Common Properties"), Description("Size of the drop shadow")]
            public double BlurRadius
            {
                get { return (double)GetValue(BlurRadiusProperty); }
                set { SetValue(BlurRadiusProperty, value); }
            }
    
            // Using a DependencyProperty as the backing store for BlurRadius.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty BlurRadiusProperty =
                DependencyProperty.Register("BlurRadius", typeof(double), typeof(FastShadow), 
                new FrameworkPropertyMetadata(10.0, 
                    FrameworkPropertyMetadataOptions.AffectsRender,
                        new PropertyChangedCallback((o, e) => {
                            FastShadow f = o as FastShadow;
                            if ((double)e.NewValue < 0)
                                f.BlurRadius = 0;
                        })));
    
    
            /// <summary>
            /// Angle of the shadow
            /// </summary>
            [Category("Common Properties"), Description("Angle of the shadow")]
            public int Direction
            {
                get { return (int)GetValue(DirectionProperty); }
                set { SetValue(DirectionProperty, value); }
            }
    
            // Using a DependencyProperty as the backing store for Direction.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty DirectionProperty =
                DependencyProperty.Register("Direction", typeof(int), typeof(FastShadow), 
                new FrameworkPropertyMetadata(315, FrameworkPropertyMetadataOptions.AffectsRender));
    
    
            #endregion Dynamic Properties
    
            #region Protected Methods
    
            protected override void OnRender(DrawingContext drawingContext)
            {
    
                double distance = Math.Max(0, ShadowDepth);
                double blurRadius = Math.Max(BlurRadius, 0);
                double angle = Direction + 45; // Make it behave the same as DropShadowEffect
    
                Rect shadowBounds = new Rect(new Point(0, 0),
                                 new Size(RenderSize.Width, RenderSize.Height));
    
                shadowBounds.Inflate(blurRadius, blurRadius);
    
                Color color = Color;
    
                // Transform angle for "Direction"
                double angleRad = angle * Math.PI / 180.0;
                double xDispl = distance;
                double yDispl = distance;
                double newX = xDispl * Math.Cos(angleRad) - yDispl * Math.Sin(angleRad);
                double newY = yDispl * Math.Cos(angleRad) + xDispl * Math.Sin(angleRad);
    
                TranslateTransform translate = new TranslateTransform(newX, newY);
                Rect transformed = translate.TransformBounds(shadowBounds);
    
                // Hint: you can make the blur radius consume more "centre"
                //       region of the bounding box by doubling this here
                // blurRadius = blurRadius * 2;
    
                // Build a set of rectangles for the shadow box
                Rect[] edges = new Rect[] { 
                    new Rect(new Point(transformed.X,transformed.Y), new Size(blurRadius,blurRadius)), // TL
                    new Rect(new Point(transformed.X+blurRadius,transformed.Y), new Size(Math.Max(transformed.Width-(blurRadius*2),0),blurRadius)), // T
                    new Rect(new Point(transformed.Right-blurRadius,transformed.Y), new Size(blurRadius,blurRadius)), // TR
                    new Rect(new Point(transformed.Right-blurRadius,transformed.Y+blurRadius), new Size(blurRadius,Math.Max(transformed.Height-(blurRadius*2),0))), // R
                    new Rect(new Point(transformed.Right-blurRadius,transformed.Bottom-blurRadius), new Size(blurRadius,blurRadius)), // BR
                    new Rect(new Point(transformed.X+blurRadius,transformed.Bottom-blurRadius), new Size(Math.Max(transformed.Width-(blurRadius*2),0),blurRadius)), // B
                    new Rect(new Point(transformed.X,transformed.Bottom-blurRadius), new Size(blurRadius,blurRadius)), // BL
                    new Rect(new Point(transformed.X,transformed.Y+blurRadius), new Size(blurRadius,Math.Max(transformed.Height-(blurRadius*2),0))), // L
                    new Rect(new Point(transformed.X+blurRadius,transformed.Y+blurRadius), new Size(Math.Max(transformed.Width-(blurRadius*2),0),Math.Max(transformed.Height-(blurRadius*2),0))), // C
                };
    
                // Gradient stops look a lot prettier than
                // a perfectly linear gradient..
                GradientStopCollection gsc = new GradientStopCollection();
                Color stopColor = color;
                stopColor.A = (byte)(color.A);
                gsc.Add(new GradientStop(color, 0.0));
                stopColor.A = (byte)(.74336 * color.A);
                gsc.Add(new GradientStop(stopColor, 0.1));
                stopColor.A = (byte)(.38053 * color.A);
                gsc.Add(new GradientStop(stopColor, 0.3));
                stopColor.A = (byte)(.12389 * color.A);
                gsc.Add(new GradientStop(stopColor, 0.5));
                stopColor.A = (byte)(.02654 * color.A);
                gsc.Add(new GradientStop(stopColor, 0.7));
                stopColor.A = (byte)(0);
                gsc.Add(new GradientStop(stopColor, 0.9));
    
                gsc.Freeze(); 
    
                Brush[] colors = new Brush[]{
                    // TL
                    new RadialGradientBrush(gsc){ Center = new Point(1, 1), GradientOrigin = new Point(1, 1), RadiusX=1, RadiusY=1},
                    // T
                    new LinearGradientBrush(gsc, 0){ StartPoint = new Point(0,1), EndPoint=new Point(0,0)},
                    // TR
                    new RadialGradientBrush(gsc){ Center = new Point(0, 1), GradientOrigin = new Point(0, 1), RadiusX=1, RadiusY=1},
                    // R
                    new LinearGradientBrush(gsc, 0){ StartPoint = new Point(0,0), EndPoint=new Point(1,0)},
                    // BR
                    new RadialGradientBrush(gsc){ Center = new Point(0, 0), GradientOrigin = new Point(0, 0), RadiusX=1, RadiusY=1},
                    // B
                    new LinearGradientBrush(gsc, 0){ StartPoint = new Point(0,0), EndPoint=new Point(0,1)},
                    // BL
                    new RadialGradientBrush(gsc){ Center = new Point(1, 0), GradientOrigin = new Point(1, 0), RadiusX=1, RadiusY=1},
                    // L
                    new LinearGradientBrush(gsc, 0){ StartPoint = new Point(1,0), EndPoint=new Point(0,0)},
                    // C
                    new SolidColorBrush(color), 
                };
    
                // This is a test pattern, uncomment to see how I'm drawing this
                //Brush[] colors = new Brush[]{
                //    Brushes.Red,
                //    Brushes.Green,
                //    Brushes.Blue,
                //    Brushes.Fuchsia,
                //    Brushes.Gainsboro,
                //    Brushes.LimeGreen,
                //    Brushes.Navy,
                //    Brushes.Orange,
                //    Brushes.White,
                //};
                double[] guidelineSetX = new double[] { transformed.X,
                                                        transformed.X+blurRadius,
                                                        transformed.Right-blurRadius,
                                                        transformed.Right};
    
                double[] guidelineSetY = new double[] { transformed.Y,
                                                        transformed.Y+blurRadius,
                                                        transformed.Bottom-blurRadius,
                                                        transformed.Bottom};
    
                drawingContext.PushGuidelineSet(new GuidelineSet(guidelineSetX, guidelineSetY)); 
                for (int i = 0; i < edges.Length; i++)
                {
                    drawingContext.DrawRoundedRectangle(colors[i], null, edges[i], 0.0, 0.0);
                }
                drawingContext.Pop();
            }
    
            #endregion
    
        }
    }
    

    Soo.. 用法会是这样的;

    <Sichbo:FastShadow Color="Black" ShadowDepth="0" BlurRadius="30">
    <Grid>.. content here ..</Grid>
    </Sichbo:FastShadow>
    

    .. 输出应该非常接近 PS 阴影的效果.. 显然只适用于“四四方方”的元素,但运行时的速度差异应该是白天和黑夜,动画在高分辨率下应该如丝般流畅,并且你可以让你的 UI 看起来很漂亮。

    【讨论】:

    • 天哪,这太神奇了!我一直在努力尝试从标准投影效果中获得更好的性能。很高兴我找到了这个答案!
    【解决方案2】:

    【讨论】:

    • 目前 WPF Performance Suite 用于分析 .NET 4.5 应用程序的替代方案是什么?如果您看一下我的问题here,我将不胜感激。
    【解决方案3】:

    Sichbo 的答案仍然很好,但仅适用于方形矩形。 我已经对其进行了调整以添加 CornerRadius 属性,以便能够在圆角矩形上绘制阴影,以便它与边框控件很好地配合。

    我还将渐变计算更改为类似于我计算机上实际的 DropShadowEffect 的东西,但由于它具有非常特殊的渲染,因此似乎无法完美再现。在任何情况下,您都可以尝试使用expFactorattenuationFactor 来适应您的需求。 expFactor 控制阴影消失的速度,attenuationFactor 通过偏移范围使其更轻(而不是从 100% 到 0% 阴影,我们从例如 95% 到 -5%,上限为 0 %)。

    渐变是在设置相关属性时计算的,而不是在每次渲染调用时计算的。

    namespace CustomControls
    {
        using System;
        using System.ComponentModel;
        using System.Windows;
        using System.Windows.Controls;
        using System.Windows.Media;
    
        /// <summary>
        /// Emulates the System.Windows.Media.Effects.DropShadowEffect using rectangles and 
        /// gradients, which performs a lot better.
        /// </summary>
        public class FastShadow : Decorator
        {
            public static readonly DependencyProperty ColorProperty =
                DependencyProperty.Register("Color", typeof(Color), typeof(FastShadow),
                new FrameworkPropertyMetadata(
                    Color.FromArgb(0x71, 0x00, 0x00, 0x00),
                    FrameworkPropertyMetadataOptions.AffectsRender,
                    new PropertyChangedCallback((o, e) =>
                    {
                        FastShadow f = o as FastShadow;
                        f.CalculateGradientStops();
                    })));
    
            public static readonly DependencyProperty BlurRadiusProperty =
                DependencyProperty.Register("BlurRadius", typeof(double), typeof(FastShadow),
                new FrameworkPropertyMetadata(
                    10.0,
                    FrameworkPropertyMetadataOptions.AffectsRender,
                    new PropertyChangedCallback((o, e) =>
                    {
                        FastShadow f = o as FastShadow;
                        if ((double)e.NewValue < 0)
                            f.BlurRadius = 0;
                        f.CalculateGradientStops();
                    })));
    
            public static readonly DependencyProperty CornerRadiusProperty =
                DependencyProperty.Register("CornerRadius", typeof(double), typeof(FastShadow),
                new FrameworkPropertyMetadata(
                    0.0,
                    FrameworkPropertyMetadataOptions.AffectsRender,
                    new PropertyChangedCallback((o, e) =>
                    {
                        FastShadow f = o as FastShadow;
                        if ((double)e.NewValue < 0)
                            f.BlurRadius = 0;
                        f.CalculateGradientStops();
                    })));
    
            public static readonly DependencyProperty ShadowDepthProperty =
                DependencyProperty.Register("ShadowDepth", typeof(double), typeof(FastShadow),
                new FrameworkPropertyMetadata(
                    0.0,
                    FrameworkPropertyMetadataOptions.AffectsRender,
                    new PropertyChangedCallback((o, e) => {
                        FastShadow f = o as FastShadow;
                        if ((double)e.NewValue < 0)
                            f.ShadowDepth = 0;
                    })));
    
            public static readonly DependencyProperty DirectionProperty =
                DependencyProperty.Register("Direction", typeof(int), typeof(FastShadow),
                new FrameworkPropertyMetadata(315, FrameworkPropertyMetadataOptions.AffectsRender));
    
            private GradientStopCollection gradientStops;
            private Brush[] edgeBrushes;
            private Brush[] cornerBrushes;
    
            public FastShadow()
                : base()
            {
                this.CalculateGradientStops();
            }
    
            /// <summary>
            /// Color used to fill the shadow region.
            /// </summary>
            [Category("Common Properties")]
            public Color Color
            {
                get => (Color)this.GetValue(ColorProperty);
                set
                {
                    this.SetValue(ColorProperty, value);
                }
            }
    
            /// <summary>
            /// Size of the shadow.
            /// </summary>
            [Category("Common Properties")]
            [Description("Size of the drop shadow")]
            public double BlurRadius
            {
                get => (double)this.GetValue(BlurRadiusProperty);
                set
                {
                    this.SetValue(BlurRadiusProperty, value);
                }
            }
    
            /// <summary>
            /// Radius of the corners.
            /// </summary>
            [Category("Common Properties")]
            [Description("Radius of the corners of the shadow")]
            public double CornerRadius
            {
                get => (double)this.GetValue(CornerRadiusProperty);
                set
                {
                    this.SetValue(CornerRadiusProperty, value);
                }
            }
    
            /// <summary>
            /// Distance from centre.
            /// </summary>
            [Category("Common Properties")]
            [Description("Distance from centre")]
            public double ShadowDepth
            {
                get { return (double)this.GetValue(ShadowDepthProperty); }
                set { this.SetValue(ShadowDepthProperty, value); }
            }
    
            /// <summary>
            /// Angle of the shadow
            /// </summary>
            [Category("Common Properties")]
            [Description("Angle of the shadow")]
            public int Direction
            {
                get { return (int)this.GetValue(DirectionProperty); }
                set { this.SetValue(DirectionProperty, value); }
            }
    
            /// <summary>
            /// Calculate gradient stops for an exponential gradient.
            /// It is designed to look similar to the WPF DropShadowEffect, but since that one renders
            /// differently depending on how zoomed in you are, a perfect fit seems impossible.
            /// </summary>
            protected void CalculateGradientStops()
            {
                double blurRadius = Math.Max(this.BlurRadius, 0);
                double cornerRadius = Math.Max(this.CornerRadius, 0);
                
                // Portion of the gradient which is drawn "inside" the border
                double innerPart = (cornerRadius - blurRadius / 20) / (cornerRadius + blurRadius); 
                double remaining = 1.0 - innerPart;
                double expFactor = 1.5 + blurRadius / 50;
                double attenuationFactor = Math.Max(0.3 - blurRadius / 50, 0.05);
    
                GradientStopCollection gsc = new GradientStopCollection();
                float[] stops = new float[] { 0.0f, 0.025f, 0.05f, 0.075f, 0.1f, 0.125f,
                0.015f, 0.2f, 0.25f, 0.3f, 0.35f, 0.4f, 0.45f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f };
                Color stopColor = this.Color;
    
                gsc.Add(new GradientStop(stopColor, 0.0));
                gsc.Add(new GradientStop(stopColor, innerPart));
                foreach (float stop in stops)
                {
                    // Exponential gradient from 1 to 0
                    double num = (Math.Exp(1 - expFactor * (stop + attenuationFactor)) - Math.Exp(1 - expFactor))
                    double det = (Math.E - Math.Exp(1 - expFactor)));
                    double factor = Math.Max(0, num / det);
                    stopColor.A = (byte)(factor * this.Color.A);
                    gsc.Add(new GradientStop(stopColor, innerPart + stop * remaining));
                }
    
                stopColor.A = 0;
                gsc.Add(new GradientStop(stopColor, innerPart + 1.0 * remaining));
                gsc.Freeze();
    
                this.gradientStops = gsc;
    
                this.edgeBrushes = new Brush[]
                {
                    new LinearGradientBrush(this.gradientStops, 0)
                        { StartPoint = new Point(0, 1), EndPoint = new Point(0, 0) }, // T
                    new LinearGradientBrush(this.gradientStops, 0)
                        { StartPoint = new Point(0, 0), EndPoint = new Point(1, 0) }, // R
                    new LinearGradientBrush(this.gradientStops, 0)
                        { StartPoint = new Point(0, 0), EndPoint = new Point(0, 1) }, // B
                    new LinearGradientBrush(this.gradientStops, 0)
                        { StartPoint = new Point(1, 0), EndPoint = new Point(0, 0) }, // L
                    new SolidColorBrush(this.Color),
                };
    
                for (int i = 0; i < this.edgeBrushes.Length; i++)
                    this.edgeBrushes[i].Freeze();
    
                this.cornerBrushes = new Brush[]
                {
                    new RadialGradientBrush(this.gradientStops) { Center = new Point(1, 1),
                        GradientOrigin = new Point(1, 1), RadiusX = 1, RadiusY = 1 }, // TL
                    new RadialGradientBrush(this.gradientStops) { Center = new Point(0, 1),
                        GradientOrigin = new Point(0, 1), RadiusX = 1, RadiusY = 1 }, // TR
                    new RadialGradientBrush(this.gradientStops) { Center = new Point(0, 0),
                        GradientOrigin = new Point(0, 0), RadiusX = 1, RadiusY = 1 }, // BR
                    new RadialGradientBrush(this.gradientStops) { Center = new Point(1, 0),
                        GradientOrigin = new Point(1, 0), RadiusX = 1, RadiusY = 1 }, // BL
                };
    
                for (int i = 0; i < this.cornerBrushes.Length; i++)
                    this.cornerBrushes[i].Freeze();
            }
    
            protected override void OnRender(DrawingContext drawingContext)
            {
                double distance = Math.Max(this.ShadowDepth, 0);
                double blurRadius = Math.Max(this.BlurRadius, 0);
                double cornerRadius = Math.Max(this.CornerRadius, 0);
                double totalRadius = blurRadius + cornerRadius;
                double angle = this.Direction + 45; // Make it behave the same as DropShadowEffect
    
                Rect shadowBounds = new Rect(new Point(0, 0),
                    new Size(this.RenderSize.Width, this.RenderSize.Height));
                shadowBounds.Inflate(blurRadius, blurRadius);
    
                double angleRad = angle * Math.PI / 180.0;
                double xDispl = distance;
                double yDispl = distance;
                double newX = xDispl * Math.Cos(angleRad) - yDispl * Math.Sin(angleRad);
                double newY = yDispl * Math.Cos(angleRad) + xDispl * Math.Sin(angleRad);
    
                TranslateTransform translate = new TranslateTransform(newX, newY);
                Rect shadowRenderRect = translate.TransformBounds(shadowBounds);
    
                Color color = this.Color;
    
                // Build a set of rectangles for the shadow box
                Rect[] edges = new Rect[]
                {
                    new Rect(new Point(shadowRenderRect.X + totalRadius, shadowRenderRect.Y),
                        new Size(Math.Max(shadowRenderRect.Width - (totalRadius * 2), 0), totalRadius)), // T
                    new Rect(new Point(shadowRenderRect.Right - totalRadius, shadowRenderRect.Y + totalRadius),
                        new Size(totalRadius, Math.Max(shadowRenderRect.Height - (totalRadius * 2), 0))), // R
                    new Rect(new Point(shadowRenderRect.X + totalRadius, shadowRenderRect.Bottom - totalRadius),
                        new Size(Math.Max(shadowRenderRect.Width - (totalRadius * 2), 0), totalRadius)), // B
                    new Rect(new Point(shadowRenderRect.X, shadowRenderRect.Y + totalRadius),
                        new Size(totalRadius, Math.Max(shadowRenderRect.Height - (totalRadius * 2), 0))), // L
                    new Rect(new Point(shadowRenderRect.X + totalRadius, shadowRenderRect.Y + totalRadius),
                        new Size(
                            Math.Max(shadowRenderRect.Width - (totalRadius * 2), 0),
                            Math.Max(shadowRenderRect.Height - (totalRadius * 2), 0))), // C
                };
    
                Rect[] corners = new Rect[]
                {
                    new Rect(shadowRenderRect.X, shadowRenderRect.Y, totalRadius * 2, totalRadius * 2), // TL
                    new Rect(shadowRenderRect.Right - totalRadius * 2, shadowRenderRect.Y,
                        totalRadius * 2, totalRadius * 2), // TR
                    new Rect(shadowRenderRect.Right - totalRadius * 2, shadowRenderRect.Bottom - totalRadius * 2,
                        totalRadius * 2, totalRadius * 2), // BR
                    new Rect(shadowRenderRect.X, shadowRenderRect.Bottom - totalRadius * 2,
                        totalRadius * 2, totalRadius * 2), // BL
                };
    
                double[] guidelineSetX = new double[] { 
                    shadowRenderRect.X,
                    shadowRenderRect.X + totalRadius,
                    shadowRenderRect.Right - totalRadius,
                    shadowRenderRect.Right, };
                double[] guidelineSetY = new double[] {
                    shadowRenderRect.Y,
                    shadowRenderRect.Y + totalRadius,
                    shadowRenderRect.Bottom - totalRadius,
                    shadowRenderRect.Bottom, };
    
                drawingContext.PushGuidelineSet(new GuidelineSet(guidelineSetX, guidelineSetY));
                for (int i = 0; i < edges.Length; i++)
                    drawingContext.DrawRectangle(this.edgeBrushes[i], null, edges[i]);
    
                drawingContext.DrawGeometry(this.cornerBrushes[0], null, CreateArcDrawing(corners[0], 180, 90));
                drawingContext.DrawGeometry(this.cornerBrushes[1], null, CreateArcDrawing(corners[1], 270, 90));
                drawingContext.DrawGeometry(this.cornerBrushes[2], null, CreateArcDrawing(corners[2], 0, 90));
                drawingContext.DrawGeometry(this.cornerBrushes[3], null, CreateArcDrawing(corners[3], 90, 90));
    
                drawingContext.Pop();
            }
    
            /// <summary>
            /// Create an Arc geometry drawing of an ellipse or circle.
            /// </summary>
            /// <param name="rect">Box to hold the whole ellipse described by the arc</param>
            /// <param name="startDegrees">Start angle of the arc degrees within the ellipse. 0 degrees is a line to the right.</param>
            /// <param name="sweepDegrees">Sweep angle, -ve = Counterclockwise, +ve = Clockwise</param>
            /// <returns>GeometryDrawing object</returns>
            private static PathGeometry CreateArcDrawing(Rect rect, double startDegrees, double sweepDegrees)
            {
                // degrees to radians conversion
                double startRadians = startDegrees * Math.PI / 180.0;
                double sweepRadians = sweepDegrees * Math.PI / 180.0;
    
                // x and y radius
                double dx = rect.Width / 2;
                double dy = rect.Height / 2;
    
                // determine the center point
                double xc = rect.X + dx;
                double yc = rect.Y + dy;
    
                // determine the start point
                double xs = rect.X + dx + (Math.Cos(startRadians) * dx);
                double ys = rect.Y + dy + (Math.Sin(startRadians) * dy);
    
                // determine the end point
                double xe = rect.X + dx + (Math.Cos(startRadians + sweepRadians) * dx);
                double ye = rect.Y + dy + (Math.Sin(startRadians + sweepRadians) * dy);
    
                bool isLargeArc = Math.Abs(sweepDegrees) > 180;
                SweepDirection sweepDirection = sweepDegrees < 0 ? SweepDirection.Counterclockwise : SweepDirection.Clockwise;
    
                PathGeometry pathGeometry = new PathGeometry();
                PathFigure pathFigure = new PathFigure();
    
                pathFigure.StartPoint = new Point(xc, yc);
    
                LineSegment line = new LineSegment(new Point(xs, ys), true);
                pathFigure.Segments.Add(line);
    
                pathFigure.StartPoint = new Point(xs, ys);
                ArcSegment arc = new ArcSegment(new Point(xe, ye), new Size(dx, dy), 0, isLargeArc, sweepDirection, true);
                pathFigure.Segments.Add(arc);
    
                line = new LineSegment(new Point(xc, yc), true);
                pathFigure.Segments.Add(line);
    
                pathFigure.IsFilled = true;
                pathGeometry.Figures.Add(pathFigure);
    
                return pathGeometry;
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2017-05-29
      • 2014-07-30
      • 2014-12-19
      • 2020-09-27
      • 1970-01-01
      • 2010-10-03
      • 1970-01-01
      • 1970-01-01
      • 2013-03-27
      相关资源
      最近更新 更多