Как нарисовать дугу с радиусом и углом начала и остановки

Если у меня есть следующие четыре свойства в моем DataContext элемента Canvas

Point  Center
double Radius
double StartAngle
double EndAngle

Можно ли рисовать дугу без лишнего кода?

Ответ 1

Предоставление настраиваемого компонента оказалось лучшим решением. Я использую его как в моем коде

<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. Когда SmallAngle ложно, дуга отображается счетчиком по часовой стрелке.

Реализация.

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

public sealed class Arc : Shape
{
    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(Arc)
        , new FrameworkPropertyMetadata(new Point(0, 0), FrameworkPropertyMetadataOptions.AffectsRender));


    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(Arc)
        , new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));

    public double EndAngle
    {
        get { return (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("EndAngle", typeof(double), typeof(Arc)
        , new FrameworkPropertyMetadata(Math.PI/2.0, FrameworkPropertyMetadataOptions.AffectsRender));

    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(Arc)
        , new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender));



    public bool SmallAngle
    {
        get { return (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("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
        {

            var a0 = StartAngle < 0 ? StartAngle + 2 * Math.PI : StartAngle;
            var 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;
                double t = a1;
                if ((a1-a0)>Math.PI)
                {
                    d = SweepDirection.Counterclockwise;
                }
                else
                {
                    d = 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>(1);
            segments.Add(new ArcSegment(p1, new Size(Radius, Radius), 0.0, large, d, true));

            List<PathFigure> figures = new List<PathFigure>(1);
            PathFigure pf = new PathFigure(p0, segments, true);
            pf.IsClosed = false;
            figures.Add(pf);

            Geometry g = new PathGeometry(figures, FillRule.EvenOdd, null);
            return g;
        }
    }
}

Ответ 2

Могу ли я предложить немного другое решение?

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: Это ставит центр Дуги в центр ее родителя. Обратите внимание, что элементы, такие как Grid, которые могут быть разделены, по-прежнему имеют центр, который может не быть столбцом или строкой, в которой находится Arc.