Feedback

C# - Pfeil zeichnen

Veröffentlicht von am 17.10.2006
(5 Bewertungen)
Das Zeichnen eines Pfeiles bringt mit .NET-Bordmitteln keine besonders schönen Ergebnisse. Die Klasse Arrow ermöglicht das Zeichnen eines eleganten Pfeils (über die Draw-Methode). Nebenbei kann über IsPointInside abgefragt werden, ob ein Punkt den Pfeil berührt.
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.ComponentModel;

namespace Arrows
{
   /// <summary>
   /// Verwaltet die Daten eines Pfeils, der über die Methode 
   /// <see cref="Paint"/> gezeichnet werden kann.
   /// </summary>
   /// <remarks>
   /// <para>
   /// Über die Eigenschaften <see cref="Points"/> und 
   /// <see cref="Path"/> können Sie die Punkte
   /// und den GraphicsPath abfragen die den Pfeil ausmachen.
   /// </para>
   /// <para>
   /// Die Methode <see cref="IsPointInside"/> überprüft, 
   /// ob der angegebene Punkt innerhalb des Pfeils liegt.
   /// </para>
   /// </remarks>
   public class Arrow
   {
      #region Pfeil-Eigenschaften

      private Point startPoint;
      /// <summary>
      /// Der Startpunkt des Pfeils
      /// </summary>
      public Point StartPoint
      {
         set
         {
            this.startPoint = value;

            // Die Pfeilpunkte und den Pfad berechnen
            this.path = this.CalculateGraphicsPath();
         }
         get
         {
            return this.startPoint;
         }
      }

      private Point endPoint;
      /// <summary>
      /// Der Endpunkt des Pfeils (Ende der Pfeilspitze)
      /// </summary>
      public Point EndPoint
      {
         set
         {
            this.endPoint = value;

            // Die Pfeilpunkte und den Pfad berechnen
            this.path = this.CalculateGraphicsPath();
         }
         get
         {
            return this.endPoint;
         }
      }

      private int shaftWidth;
      /// <summary>
      /// Die Breite des Pfeilschafts
      /// </summary>
      public int ShaftWidth
      {
         set
         {
            this.shaftWidth = value;

            // Die Pfeilpunkte und den Pfad berechnen
            this.path = this.CalculateGraphicsPath();
         }
         get
         {
            return this.shaftWidth;
         }
      }

      private int arrowHeadLength;
      /// <summary>
      /// Gibt die Länge der Pfeilspitze bis zum linken 
      /// bzw. rechten Eckpunkt an
      /// </summary>
      public int ArrowHeadLength
      {
         set
         {
            this.arrowHeadLength = value;

            // Die Pfeilpunkte und den Pfad berechnen
            this.path = this.CalculateGraphicsPath();
         }
         get
         {
            return this.arrowHeadLength;
         }
      }

      private int arrowHeadLengthFromShaft;
      /// <summary>
      /// Gibt die Länge der Pfeilspitze auf der Y-Linie an, 
      /// die die Mitte des Pfeilschafts bildet
      /// </summary>
      public int ArrowHeadLengthFromShaft
      {
         set
         {
            this.arrowHeadLengthFromShaft = value;

            // Die Pfeilpunkte und den Pfad berechnen
            this.path = this.CalculateGraphicsPath();
         }
         get
         {
            return this.arrowHeadLengthFromShaft;
         }
      }

      private int arrowHeadWidth;
      /// <summary>
      /// Gibt die Breite der Pfeilspitze am Anfang derselben an
      /// </summary>
      public int ArrowHeadWidth
      {
         set
         {
            this.arrowHeadWidth = value;

            // Die Pfeilpunkte und den Pfad berechnen
            this.path = this.CalculateGraphicsPath();
         }
         get
         {
            return this.arrowHeadWidth;
         }
      }

      private int arrowHeadMiddleWidth;
      /// <summary>
      /// Gibt die Breite der Pfeilspitze in der Mitte derselben an. 
      /// Ein Wert größer 0 führt dazu, dass die Seitenlinien der 
      /// Pfeilspitze als Kurve gezeichnet werden. 
      /// Bei einem Wert kleiner/gleich 0 werden diese Linien 
      /// als Gerade gezeichnet.
      /// </summary>
      public int ArrowHeadMiddleWidth
      {
         set
         {
            this.arrowHeadMiddleWidth = value;

            // Die Pfeilpunkte und den Pfad berechnen
            this.path = this.CalculateGraphicsPath();
         }
         get
         {
            return this.arrowHeadMiddleWidth;
         }
      }

      private float arrowHeadCurveTension = 1;
      /// <summary>
      /// Gibt die Krümmung der Seitenlinien der Pfeilspitze 
      /// mit einem Wert zwischen 0 (keine Krümmung) und 1 
      /// (maximale Krümmung, Default) an (Werte größer 1 
      /// führen zu eigenartigen Ergebnissen). 
      /// Gilt nur, wenn <see cref="ArrowHeadMiddleWidth"/> 
      /// größer 0 ist.
      /// </summary>
      public float ArrowHeadCurveTension
      {
         set
         {
            this.arrowHeadCurveTension = value;

            // Die Pfeilpunkte und den Pfad berechnen
            this.path = this.CalculateGraphicsPath();
         }
         get
         {
            return this.arrowHeadCurveTension;
         }
      }

      #endregion

      #region Konstruktoren

      /// <summary>
      /// Konstruktor
      /// </summary>
      /// <param name="startPoint">Der Startpunkt des Pfeils</param>
      /// <param name="endPoint">Der Endpunkt des Pfeils (Ende der Pfeilspitze)</param>
      /// <param name="ahsftWidth">Die Breite des Pfeilschafts</param>
      /// <param name="arrowHeadLength">Gibt die Länge der Pfeilspitze 
      /// bis zum linken bzw. rechten Eckpunkt an</param>
      /// <param name="arrowHeadLengthFromShaft">Gibt die Länge der 
      /// Pfeilspitze auf der Y-Linie an, die die Mitte des Pfeilschafts bildet</param>
      /// <param name="arrowHeadWidth">Gibt die Breite der Pfeilspitze 
      /// am Anfang derselben an</param>
      /// <param name="arrowHeadMiddleWidth">Gibt die Breite der Pfeilspitze 
      /// in der Mitte derselben an. Ein Wert größer 0 führt dazu, 
      /// dass die Seitenlinien der Pfeilspitze als Kurve gezeichnet werden. 
      /// Bei einem Wert kleiner/gleich 0 werden diese Linien als Gerade gezeichnet.</param>
      public Arrow(Point startPoint, Point endPoint, int shaftWidth, int arrowHeadLength, 
         int arrowHeadLengthFromShaft, int arrowHeadWidth, int arrowHeadMiddleWidth)
      {
         // Werte übergeben
         this.startPoint = startPoint;
         this.endPoint = endPoint;
         this.shaftWidth = shaftWidth;
         this.arrowHeadLength = arrowHeadLength;
         this.ArrowHeadLengthFromShaft = arrowHeadLengthFromShaft;
         this.arrowHeadWidth = arrowHeadWidth;
         this.arrowHeadMiddleWidth = arrowHeadMiddleWidth;

         // Die Pfeilpunkte und den Pfad berechnen
         this.path = this.CalculateGraphicsPath();
      }

      /// <summary>
      /// Konstruktor
      /// </summary>
      /// <param name="startPoint">Der Startpunkt des Pfeils</param>
      /// <param name="endPoint">Der Endpunkt des Pfeils (Ende der Pfeilspitze)</param>
      /// <param name="shaftWidth">Die Breite des Pfeilschafts</param>
      /// <param name="arrowHeadLength">Gibt die Länge der Pfeilspitze bis zum 
      /// linken bzw. rechten Eckpunkt an</param>
      /// <param name="arrowHeadLengthFromShaft">Gibt die Länge der Pfeilspitze 
      /// auf der Y-Linie an, die die Mitte des Pfeilschafts bildet</param>
      /// <param name="arrowHeadWidth">Gibt die Breite der Pfeilspitze am 
      /// Anfang derselben an</param>
      /// <param name="arrowHeadMiddleWidth">Gibt die Breite der Pfeilspitze 
      /// in der Mitte derselben an. Ein Wert größer 0 führt dazu, dass die 
      /// Seitenlinien der Pfeilspitze als Kurve gezeichnet werden. Bei einem 
      /// Wert kleiner/gleich 0 werden diese Linien als Gerade gezeichnet.</param>
      /// <param name="arrowHeadCurveTension">Gibt die Krümmung der Seitenlinien 
      /// der Pfeilspitze mit einem Wert zwischen 0 (keine Krümmung) und 1 
      /// (maximale Krümmung, Default) an (Werte größer 1 führen zu 
      /// eigenartigen Ergebnissen). Gilt nur, wenn <see cref="ArrowHeadMiddleWidth"/> 
      /// größer 0 ist</param>
      public Arrow(Point startPoint, Point endPoint, int shaftWidth, 
         int arrowHeadLength, int arrowHeadLengthFromShaft, int arrowHeadWidth, 
         int arrowHeadMiddleWidth, float arrowHeadCurveTension)
      {
         this.startPoint = startPoint;
         this.endPoint = endPoint;
         this.shaftWidth = shaftWidth;
         this.arrowHeadLength = arrowHeadLength;
         this.ArrowHeadLengthFromShaft = arrowHeadLengthFromShaft;
         this.arrowHeadWidth = arrowHeadWidth;
         this.arrowHeadMiddleWidth = arrowHeadMiddleWidth;
         this.arrowHeadCurveTension = arrowHeadCurveTension;

         // Die Pfeilpunkte und den Pfad berechnen
         this.path = this.CalculateGraphicsPath();
      }

      #endregion

      #region Berechnete Eigenschaften

      /// <summary>
      /// Gibt die Länge des Pfeils zurück
      /// </summary>
      public int Length
      {
         get
         {
            // Länge des Pfeils berechnen und zurückgeben
            int x1 = Math.Abs(startPoint.X);
            int y1 = Math.Abs(startPoint.Y);
            int x2 = Math.Abs(endPoint.X);
            int y2 = Math.Abs(endPoint.Y);
            int a = x2 - x1;
            int b = y2 - y1;
            return (int)Math.Sqrt(Math.Pow(a, 2) + Math.Pow(b, 2));
         }
      }

      /// <summary>
      /// Gibt den Mittelpunkt des Pfeils zurück
      /// </summary>
      public PointF CenterPoint
      {
         get
         {
            float xDiff = (this.endPoint.X - this.startPoint.X) / 2F;
            float yDiff = (this.endPoint.Y - this.startPoint.Y) / 2F;
            return new PointF(startPoint.X + xDiff, startPoint.Y + yDiff);
         }
      }

      private Point[] points;
      /// <summary>
      /// Berechnet die Punkte, die den Pfeil ausmachen. Der erste Punkt ist der
      /// Punkt am linken Ende des Pfeilschafts. Gezählt wird im Uhrzeigersinn.
      /// Ist <see cref="ArrowHeadMiddleWidth"/> größer 0 enthält das Array 9 Punkte.
      /// In diesem Fall stellen die Punkte 3 und 5 die Mitte der Kurven der
      /// Pfeilspitze dar. Ansonsten anthält das Array lediglich 7 Punkte.
      /// </summary>
      /// <remarks>
      /// Wird automatisch im Konstruktor und bei jeder Änderung 
      /// einer der Pfeil-bildenden Eigenschaften berechnet.
      /// </remarks>
      public Point[] Points
      {
         get
         {
            return this.points;
         }
      }

      private GraphicsPath path;
      /// <summary>
      /// Gibt den GraphicsPath zurück, der den Pfeil bildet
      /// </summary>
      /// <remarks>
      /// Wird automatisch im Konstruktor und bei jeder Änderung 
      /// einer der Pfeil-bildenden Eigenschaften berechnet.
      /// </remarks>
      public GraphicsPath Path
      {
         get
         {
            return this.path;
         }
      }

      #endregion

      #region Methoden

      /// <summary>
      /// Zeichnet den Pfeil
      /// </summary>
      /// <param name="g">Das Graphics-Objekt, auf dem gezeichnet werden soll</param>
      /// <param name="pen">Der Pen, mit dem der Rand gezeichnet werden soll</param>
      /// <param name="brush">Der Brush für die Füllung</param>
      public void Paint(Graphics g, Pen pen, Brush brush)
      {
         // Den GraphicsPath zeichnen
         g.FillPath(brush, this.path);
         g.DrawPath(pen, this.path);
      }

      /// <summary>
      /// Ermittelt, ob der angegebene Punkt innerhalb des Pfeils liegt
      /// </summary>
      public bool IsPointInside(Point point)
      {
         return this.path.IsVisible(point);
      }

      #endregion

      #region Private Methoden

      /// <summary>
      /// Berechnet die Punkte, die den Pfeil ausmachen
      /// </summary>
      private Point[] CalculateArrowPoints()
      {
         // Länge des Pfeils ermitteln
         int arrowLength = this.Length;

         // Der Pfeil wird virtuell so gezeichnet, dass die Spitze 
         // auf dem Punkt (0,0) liegt und der Anfang auf dem Punkt (0, Pfeillänge): 
         // Virtuellen Start- und Endpunkt berechnen
         int widthOffset = (this.shaftWidth / 2);
         Point virtualEndPoint = new Point(0, 0);
         Point virtualStartPoint = new Point(virtualEndPoint.X, 
            virtualEndPoint.Y + arrowLength);
         Point virtualCenterPoint = new Point(virtualEndPoint.X, 
            virtualEndPoint.Y + (int)(arrowLength / 2F));

         // Den Mittelpunkt des Pfeils ermitteln
         PointF centerPoint = this.CenterPoint;

         // Der Pfeil wird beim Zeichnen so transformiert, dass er korrekt gedreht
         // und verschoben gezeichnet wird:
         // Transformation für die gegebenenfalls notwendige Drehung berechnen und anwenden
         float rotationAngle = this.CalcAngleFromPoint(
            new Point((int)(this.endPoint.X - centerPoint.X),
            (int)(this.endPoint.Y - centerPoint.Y)));
         Matrix matrix = new Matrix();
         matrix.RotateAt(rotationAngle, virtualCenterPoint, MatrixOrder.Append);
         // Verschiebung berechnen und anwenden
         float offsetX = centerPoint.X - virtualCenterPoint.X;
         float offsetY = centerPoint.Y - virtualCenterPoint.Y;
         matrix.Translate(offsetX, offsetY, MatrixOrder.Append);

         // Punkte für den Pfeil zusammenstellen
         Point[] arrowPoints;
         if (this.arrowHeadMiddleWidth > 0)
         {
            arrowPoints = new Point[9];
            // Linkes unteres Ende des Pfeilschafts
            arrowPoints[0] = new Point(widthOffset * -1, virtualStartPoint.Y);
            // Linkes oberes Ende des Pfeilschafts 
            arrowPoints[1] = new Point(arrowPoints[0].X, this.arrowHeadLength);
            // Linke untere Ecke der Pfeilspitze
            arrowPoints[2] = new Point((this.arrowHeadWidth / 2) * -1, 
               this.arrowHeadLengthFromShaft);
            // Mitte der Pfeilspize links (für eine gerümmte Pfeilspitze)
            arrowPoints[3] = new Point(-(this.arrowHeadMiddleWidth / 2), 
               this.arrowHeadLengthFromShaft / 2);
            // Pfeilspitze
            arrowPoints[4] = new Point(0, 0);
            // Mitte der Pfeilspitze rechts (für eine gekrümmte Pfeilspitze)
            arrowPoints[5] = new Point(this.arrowHeadMiddleWidth / 2, 
               this.arrowHeadLengthFromShaft / 2);
            // Rechte untere Ecke der Pfeilspitze
            arrowPoints[6] = new Point(this.arrowHeadWidth / 2, 
               this.arrowHeadLengthFromShaft);
            // Rechtes oberes Ende des Pfeilschafts
            arrowPoints[7] = new Point(arrowPoints[1].X + this.shaftWidth, 
               arrowPoints[1].Y);
            // Rechtes unteres Ende des Pfeilschafts
            arrowPoints[8] = new Point(arrowPoints[0].X + this.shaftWidth, 
               arrowPoints[0].Y);
         }
         else
         {
            arrowPoints = new Point[7];
            // Linkes unteres Ende des Pfeilschafts
            arrowPoints[0] = new Point(widthOffset * -1, virtualStartPoint.Y);
            // Linkes oberes Ende des Pfeilschafts 
            arrowPoints[1] = new Point(arrowPoints[0].X, this.arrowHeadLength);
            // Linke untere Ecke der Pfeilspitze
            arrowPoints[2] = new Point((this.arrowHeadWidth / 2) * -1, 
               this.arrowHeadLengthFromShaft);
            // Pfeilspitze
            arrowPoints[3] = new Point(0, 0);
            // Rechte untere Ecke der Pfeilspitze
            arrowPoints[4] = new Point(this.arrowHeadWidth / 2, 
               this.arrowHeadLengthFromShaft);
            // Rechtes oberes Ende des Pfeilschafts
            arrowPoints[5] = new Point(arrowPoints[1].X + this.shaftWidth, 
               arrowPoints[1].Y);
            // Rechtes unteres Ende des Pfeilschafts
            arrowPoints[6] = new Point(arrowPoints[0].X + this.shaftWidth, 
               arrowPoints[0].Y);
         }

         // Die Punkte transformieren
         matrix.TransformPoints(arrowPoints);

         // und zurückgeben
         return arrowPoints;
      }

      /// <summary>
      /// Berechnet den Pfad, der den Pfeil darstellt
      /// </summary>
      private GraphicsPath CalculateGraphicsPath()
      {
         // Punkte berechnen
         this.points = this.CalculateArrowPoints();

         // GraphicsPath erzeugen
         GraphicsPath path = new GraphicsPath();

         // Pfad zusammenstellen
         if (this.arrowHeadMiddleWidth > 0)
         {
            // Aus den Punkten einen Pfad mit gekrümmter Pfeilspitze bilden
            path.AddLine(this.points[0], this.points[1]);
            path.AddLine(this.points[1], this.points[2]);
            path.AddCurve(new Point[] { this.points[2], this.points[3], 
               this.points[4] }, this.arrowHeadCurveTension);
            path.AddCurve(new Point[] { this.points[4], this.points[5], 
               this.points[6] }, this.arrowHeadCurveTension);
            path.AddLine(this.points[6], this.points[7]);
            path.AddLine(this.points[7], this.points[8]);
         }
         else
         {
            // Aus den Punkten einen Pfad mit gerader Pfeilspitze bilden
            path.AddLines(this.points);
         }

         // Den Pfad schließen
         path.CloseFigure();

         // und zurückgeben
         return path;
      }

      /// <summary>
      /// Berechnet für einen Kreis den Winkel, den der übergebene 
      /// Punkt bezogen auf den Mittelpunkt des Kreises ergibt. 
      /// Der Winkel bezieht sich auf die 12-Uhr-Position des Kreises.
      /// </summary>
      /// <param name="point">Der Punkt, der den X- und den Y-Abstand 
      /// zum Mittelpunkt angibt</param>
      /// <returns>Gibt den berechneten Winkel zurück</returns>
      private float CalcAngleFromPoint(Point point)
      {
         // Je nach Quadrant wird anders gerechnet
         int a, b;
         int angleOffset;
         // a = Ankathete
         // b = Gegenkathete
         // Grundrechnung: Tan(alpha) = b / a
         // => alpha = Atan(b / a)
         if (point.X >= 0 && point.Y < 0)
         {
            // Erster Quadrant
            a = point.X;
            b = point.Y * -1;
            angleOffset = 90;
         }
         else if (point.X >= 0 && point.Y >= 0)
         {
            // Zweiter Quadrant
            a = point.Y;
            b = point.X;
            angleOffset = 180;
         }
         else if (point.X < 0 && point.Y >= 0)
         {
            // Dritter Quadrant
            a = point.X * -1;
            b = point.Y;
            angleOffset = 270;
         }
         else
         {
            // Vierter Quadrant
            b = point.X * -1;
            a = point.Y * -1;
            angleOffset = 360;
         }

         // Winkel im Bogenmaß berechnen
         double radian = Math.Atan((double)b / (double)a);

         // Winkel in Grad umrechnen und zurückgeben
         return angleOffset - (float)(radian * (180 / Math.PI));
      }

      #endregion
   }
}

Abgelegt unter GDI+, Pfeil, zeichnen.

Kommentare zum Snippet

 

Logge dich ein, um hier zu kommentieren!