如何用半径绘制圆弧并开始和停止角度

时间:2013-05-21 09:53:18

标签: c# wpf xaml geometry

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

Point  Center
double Radius
double StartAngle
double EndAngle

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

2 个答案:

答案 0 :(得分:24)

提供自定义组件是最佳解决方案。我在我的代码

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

SmallAngletrue呈现点之间的小角度时,无论StartAngleEndAngle的顺序如何。当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);
        }
    }
}

答案 1 :(得分:3)

我可以提供略有不同的解决方案吗?

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所在的列或行。