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
}
}