WPF 动画/UI 功能性能和基准测试
Posted
技术标签:
【中文标题】WPF 动画/UI 功能性能和基准测试【英文标题】:WPF animation/UI features performance and benchmarking 【发布时间】:2009-06-07 13:20:00 【问题描述】:我正在为一些业务线的一些花哨的 WPF UI 工作做一个相对较小的概念验证。甚至不用太疯狂,当我使用许多我认为是首先考虑将 WPF 用于 UI 构建的主要原因时,我已经看到了一些非常糟糕的性能。我在这里问了一个问题,为什么我的动画在第一次运行时就停止了,最后我发现一个非常简单的 UserControl 只需要半秒钟来构建它的可视化树。我能够解决该症状,但是初始化一个简单控件需要很长时间这一事实确实让我感到困扰。现在,我正在测试有无 DropShadowEffect 的动画,结果是白天黑夜。一个微妙的阴影让我的控件看起来好多了,但它完全破坏了动画的平滑度。让我也不要从字体渲染开始。当控件有一堆渐变画笔和一个阴影时,我的动画计算使文本模糊了大约整整一秒钟,然后慢慢变成焦点。
所以,我想我的问题是,是否有已知的研究、博客文章或文章详细说明当前 WPF 版本中哪些功能对业务关键型应用程序存在危害。效果(即 DropShadowEffect)、渐变画笔、关键帧动画等是否会对渲染质量(或者这些东西的组合)产生过多的负面影响? WPF 4.0 的最终版本会纠正其中的一些问题吗?我已经读到 VS2010 beta 有一些同样的问题,并且它们应该在最终版本中得到解决。这是因为 WPF 本身的改进,还是因为一半的应用程序将使用以前的技术重建?
【问题讨论】:
【参考方案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】:你看过微软的文章吗?
Optimizing WPF Application Performance Performance Profiling Tools for WPF【讨论】:
目前 WPF Performance Suite 用于分析 .NET 4.5 应用程序的替代方案是什么?如果您看一下我的问题here,我将不胜感激。【参考方案3】:Sichbo 的答案仍然很好,但仅适用于方形矩形。 我已经对其进行了调整以添加 CornerRadius 属性,以便能够在圆角矩形上绘制阴影,以便它与边框控件很好地配合。
我还将渐变计算更改为类似于我计算机上实际的 DropShadowEffect 的东西,但由于它具有非常特殊的渲染,因此似乎无法完美再现。在任何情况下,您都可以尝试使用expFactor
和attenuationFactor
来适应您的需求。 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;
【讨论】:
以上是关于WPF 动画/UI 功能性能和基准测试的主要内容,如果未能解决你的问题,请参考以下文章