17

如果我的 Canvas 元素的 DataContext 中有以下四个属性

Point  Center
double Radius
double StartAngle
double EndAngle

我可以在没有任何额外代码的情况下绘制弧线吗?

4

2 回答 2

31

事实证明,提供自定义组件是最好的解决方案。我在我的代码中这样使用它

<Controls:Arc Center="{Binding Path=PreviousMousePositionPixels}" 
         Stroke="White" 
         StrokeDashArray="4 4"
         SnapsToDevicePixels="True"
         StartAngle="0" 
         EndAngle="{Binding Path=DeltaAngle}" 
         SmallAngle="True"
         Radius="40" />

SmallAngle何时true将呈现点之间的小角度,而与StartAngle和的顺序无关EndAngle。弧线何时逆时针呈现SmallAnglefalse

实现是

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;

public sealed class Arc : Shape
{
    public Point Center
    {
        get => (Point)GetValue(CenterProperty);
        set => SetValue(CenterProperty, value);
    }

    // Using a DependencyProperty as the backing store for Center.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty CenterProperty = 
        DependencyProperty.Register(nameof(Center), typeof(Point), typeof(Arc), 
            new FrameworkPropertyMetadata(new Point(), FrameworkPropertyMetadataOptions.AffectsRender));

    public double StartAngle
    {
        get => (double)GetValue(StartAngleProperty);
        set => SetValue(StartAngleProperty, value);
    }

    // Using a DependencyProperty as the backing store for StartAngle.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty StartAngleProperty =
        DependencyProperty.Register(nameof(StartAngle), typeof(double), typeof(Arc),
            new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));

    public double EndAngle
    {
        get => (double)GetValue(EndAngleProperty);
        set => SetValue(EndAngleProperty, value);
    }

    // Using a DependencyProperty as the backing store for EndAngle.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty EndAngleProperty =
        DependencyProperty.Register(nameof(EndAngle), typeof(double), typeof(Arc),
            new FrameworkPropertyMetadata(Math.PI / 2.0, FrameworkPropertyMetadataOptions.AffectsRender));

    public double Radius
    {
        get => (double)GetValue(RadiusProperty);
        set => SetValue(RadiusProperty, value);
    }

    // Using a DependencyProperty as the backing store for Radius.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty RadiusProperty =
        DependencyProperty.Register(nameof(Radius), typeof(double), typeof(Arc),
            new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender));

    public bool SmallAngle
    {
        get => (bool)GetValue(SmallAngleProperty);
        set => SetValue(SmallAngleProperty, value);
    }

    // Using a DependencyProperty as the backing store for SmallAngle.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SmallAngleProperty =
        DependencyProperty.Register(nameof(SmallAngle), typeof(bool), typeof(Arc),
            new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));

    static Arc() => DefaultStyleKeyProperty.OverrideMetadata(typeof(Arc), new FrameworkPropertyMetadata(typeof(Arc)));

    protected override Geometry DefiningGeometry
    {
        get
        {
            double a0 = StartAngle < 0 ? StartAngle + 2 * Math.PI : StartAngle;
            double a1 = EndAngle < 0 ? EndAngle + 2 * Math.PI : EndAngle;

            if (a1 < a0)
                a1 += Math.PI * 2;

            SweepDirection d = SweepDirection.Counterclockwise;
            bool large;

            if (SmallAngle)
            {
                large = false;
                d = (a1 - a0) > Math.PI ? SweepDirection.Counterclockwise : SweepDirection.Clockwise;
            }
            else
                large = (Math.Abs(a1 - a0) < Math.PI);

            Point p0 = Center + new Vector(Math.Cos(a0), Math.Sin(a0)) * Radius;
            Point p1 = Center + new Vector(Math.Cos(a1), Math.Sin(a1)) * Radius;

            List<PathSegment> segments = new List<PathSegment>
            {
                new ArcSegment(p1, new Size(Radius, Radius), 0.0, large, d, true)
            };

            List<PathFigure> figures = new List<PathFigure>
            {
                new PathFigure(p0, segments, true)
                {
                    IsClosed = false
                }
            };

            return new PathGeometry(figures, FillRule.EvenOdd, null);
        }
    }
}
于 2013-05-21T12:08:31.607 回答
4

我可以提供一个稍微不同的解决方案吗?

class ArcII:FrameworkElement
{
    /// <summary>
    /// Center point of Arc.
    /// </summary>
    [Category("Arc")]
    public Point Center
    {
        get { return (Point)GetValue(CenterProperty); }
        set { SetValue(CenterProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Center.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty CenterProperty =
        DependencyProperty.Register("Center", typeof(Point), typeof(ArcII), new FrameworkPropertyMetadata(new Point(0, 0), FrameworkPropertyMetadataOptions.AffectsRender));
    
    /// <summary>
    /// Forces the Arc to the center of the Parent container.
    /// </summary>
    [Category("Arc")]
    public bool OverrideCenter
    {
        get { return (bool)GetValue(OverrideCenterProperty); }
        set { SetValue(OverrideCenterProperty, value); }
    }

    // Using a DependencyProperty as the backing store for OverrideCenter.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty OverrideCenterProperty =
        DependencyProperty.Register("OverrideCenter", typeof(bool), typeof(ArcII), new FrameworkPropertyMetadata((bool)false, FrameworkPropertyMetadataOptions.AffectsRender));

    /// <summary>
    /// Start angle of arc, using standard coordinates. (Zero is right, CCW positive direction)
    /// </summary>
    [Category("Arc")]
    public double StartAngle
    {
        get { return (double)GetValue(StartAngleProperty); }
        set { SetValue(StartAngleProperty, value); }
    }

    // Using a DependencyProperty as the backing store for StartAngle.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty StartAngleProperty =
        DependencyProperty.Register("StartAngle", typeof(double), typeof(ArcII), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));

    /// <summary>
    /// Length of Arc in degrees.
    /// </summary>
    [Category("Arc")]
    public double SweepAngle
    {
        get { return (double)GetValue(SweepAngleProperty); }
        set { SetValue(SweepAngleProperty, value); }
    }

    // Using a DependencyProperty as the backing store for SweepAngle.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SweepAngleProperty =
        DependencyProperty.Register("SweepAngle", typeof(double), typeof(ArcII), new FrameworkPropertyMetadata((double)180, FrameworkPropertyMetadataOptions.AffectsRender));

    /// <summary>
    /// Size of Arc.
    /// </summary>
    [Category("Arc")]
    public double Radius
    {
        get { return (double)GetValue(RadiusProperty); }
        set { SetValue(RadiusProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Radius.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty RadiusProperty =
        DependencyProperty.Register("Radius", typeof(double), typeof(ArcII), new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender));

    [Category("Arc")]
    public Brush Stroke
    {
        get { return (Brush)GetValue(StrokeProperty); }
        set { SetValue(StrokeProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Stroke.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty StrokeProperty =
        DependencyProperty.Register("Stroke", typeof(Brush), typeof(ArcII), new FrameworkPropertyMetadata((Brush)Brushes.Black,FrameworkPropertyMetadataOptions.AffectsRender));

    [Category("Arc")]
    public double StrokeThickness
    {
        get { return (double)GetValue(StrokeThicknessProperty); }
        set { SetValue(StrokeThicknessProperty, value); }
    }

    // Using a DependencyProperty as the backing store for StrokeThickness.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty StrokeThicknessProperty =
        DependencyProperty.Register("StrokeThickness", typeof(double), typeof(ArcII), new FrameworkPropertyMetadata((double)1,FrameworkPropertyMetadataOptions.AffectsRender));

    protected override void OnRender(DrawingContext dc)
    {
        base.OnRender(dc);
        Draw(dc);
    }

    private void Draw(DrawingContext dc)
    {
        Point center = new Point();
        if (OverrideCenter)
        {
            Rect rect = new Rect(RenderSize);
            center = Polar.CenterPointFromRect(rect);
        }
        else
        {
            center = Center;
        }

        Point startPoint = Polar.PolarToCartesian(StartAngle, Radius, center);
        Point endPoint = Polar.PolarToCartesian(StartAngle + SweepAngle, Radius, center);
        Size size = new Size(Radius, Radius);

        bool isLarge = (StartAngle + SweepAngle) - StartAngle > 180;

        List<PathSegment> segments = new List<PathSegment>(1);
        segments.Add(new ArcSegment(endPoint, new Size(Radius, Radius), 0.0, isLarge, SweepDirection.Clockwise, true));

        List<PathFigure> figures = new List<PathFigure>(1);
        PathFigure pf = new PathFigure(startPoint, segments, true);
        pf.IsClosed = false;
        figures.Add(pf);
        Geometry g = new PathGeometry(figures, FillRule.EvenOdd, null);

        dc.DrawGeometry(null, new Pen(Stroke,StrokeThickness), g);
    }
}

用法:

    <!--Centerd on Parent-->
    <local:ArcII Center="0,0"
                 OverrideCenter="True"
                 StartAngle="150"
                 SweepAngle="240"
                 Radius="100"
                 Stroke="Red"
                 StrokeThickness="3"
                 />

    <!--Centerd on Parent-->
    <local:ArcII Center="0,0"
                 OverrideCenter="True"
                 StartAngle="150"
                 SweepAngle="240"
                 Radius="95"
                 Stroke="Red"
                 StrokeThickness="3"
                 />

    <!--Centerd on Parent-->
    <local:ArcII Center="0,0"
                 OverrideCenter="True"
                 StartAngle="150"
                 SweepAngle="240"
                 Radius="90"
                 Stroke="Red"
                 StrokeThickness="3"
                 />

    <!--Centerd on Point-->
    <local:ArcII Center="0,150"
                 OverrideCenter="False"
                 StartAngle="270"
                 SweepAngle="180"
                 Radius="100"
                 />

    <!--Centerd on Point-->
    <local:ArcII Center="525,150"
                 OverrideCenter="False"
                 StartAngle="90"
                 SweepAngle="180"
                 Radius="100"
                 />

注意:A)这不会做 360 SweepAngle,因为它使用椭圆。B) OverrideCenter:这将 Arc 的中心置于其父级的中心。请注意,像 Grid 这样可以分区的元素仍然有一个中心,它可能不是 Arc 所在的列或行。

抱歉,我来这里已经有一段时间了。更新以提供极坐标类...

public static class Polar
{
    /// <summary>
    /// Given the center of a circle and its radius, along with the angle 
    /// corresponding to the point, find the coordinates.  In other words, 
    /// convert from polar to rectangular coordinates.
    /// </summary>
    /// <param name="angle"></param>
    /// <param name="radius"></param>
    /// <param name="center"></param>
    /// <returns></returns>
    public static Point PolarToCartesian(double angle, double radius, Point center)
    {
        return new Point((center.X + (radius * Math.Cos(DegreesToRadian(angle)))), (center.Y + (radius * Math.Sin(DegreesToRadian(angle)))));
    }


    /// <summary>
    /// Given a center point and radius, find the top left point for a rectangle and its size.
    /// </summary>
    /// <param name="centerPoint"></param>
    /// <param name="radius"></param>
    /// <returns></returns>
    public static Rect RectFromCenterPoint(Point centerPoint, int radius)
    {
        Point p = new Point(centerPoint.X - radius, centerPoint.Y - radius);
        return new Rect(p, new Size(radius * 2, radius * 2));
    }

    /// <summary>
    /// Finds the center point of a Rect
    /// </summary>
    /// <param name="rect"></param>
    /// <returns></returns>
    public static Point CenterPoint(Rect rect)
    {
        return new Point(rect.Width / 2, rect.Height / 2);
    }

    /// <summary>
    /// Returns a radius value equal to the smallest side.
    /// </summary>
    /// <param name="rect"></param>
    /// <returns></returns>
    public static double Radius(Rect rect)
    {
        double dbl = Math.Min(rect.Width, rect.Height);
        return dbl / 2;
    }


    /// <summary>
    /// Since Windows Forms consider an Angle of Zero to be at the 3:00 position and an Angle of 90
    /// to be at the 12:00 position, it is sometimes difficult to visualize where 
    /// 
    /// </summary>
    /// <param name="Angle"></param>
    /// <param name="Offset"></param>
    /// <returns></returns>
    /// <remarks></remarks>
    public static float ReversePolarDirection(float Angle, int Offset)
    {
        return ((360 - Angle) + Offset) % 360;
    }

    /// <summary>
    /// Circumference: C = 2*Pi*r = Pi*d; r=Radius, d=Diameter
    /// </summary>
    /// <param name="Diameter"></param>
    /// <returns></returns>
    /// <remarks></remarks>
    public static double CircumferenceD(double Diameter)
    {
        return Diameter * Math.PI;
    }
    /// <summary>
    /// Circumference: C = 2*Pi*r = Pi*d; r=Radius, d=Diameter
    /// </summary>
    /// <param name="Radius"></param>
    /// <returns></returns>
    /// <remarks></remarks>
    public static double CircumferenceR(double Radius)
    {
        return Radius * Math.PI;
    }
    public static double ScaleWithParam(double Input, double InputMin, double InputMax, double ScaledMin, double ScaledMax)
    {
        //Out = (((ScMax-ScMin)/(InMax-InMin))*Input)+(ScMin-(InMin*((ScMax-ScMin)/(InMax-InMin))
        return (((ScaledMax - ScaledMin) / (InputMax - InputMin)) * Input) + (ScaledMin - (InputMin * ((ScaledMax - ScaledMin) / (InputMax - InputMin))));

    }
    public static double DegreesToRadian(double degrees)
    {
        //Return 2 * Math.PI * degrees / 360.0
        return degrees * (Math.PI / 180);
    }
    private static double RadianToDegrees(double radian)
    {
        return radian * 180 / Math.PI;
    }

    public static double ArcLength(double radius, double radian)
    {
        return radius * radian;
    }
}
于 2017-05-07T14:18:29.463 回答