using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Collections.ObjectModel;
namespace AlexanderFillbrunn.Mousegestures
{
/// <summary>
/// Stellt Mausgestenerkennung für ein Control zur Verfügung
/// </summary>
public class GestureProvider
{
//gibt an, ob die Maustaste gedrückt gehalten wird
bool mouseDown = false;
//Die Position der Maus auf dem Steuerelement
System.Drawing.Point mousePosition;
//Der Startpunkt einer neuen Linie
System.Drawing.Point start = new System.Drawing.Point(-1, -1);
//Liste aller Linien einer Geste
List<Line> lines = new List<Line>();
//Gibt an, ob die gerade gezeichnete Geste gültig ist
bool validGesture = false;
/// <summary>
/// Erzeugt eine neue Instanz eines GestureProviders
/// </summary>
/// <param name="control">Das Steuerelement, auf dem die Mausgesten ausgeführt werden</param>
/// <param name="mouseButton">Die Maustaste, die gedrückt wird, während die Geste ausgeführt wird</param>
public GestureProvider(System.Windows.Forms.Control control, MouseButtons mouseButton)
{
this.control = control;
this.button = mouseButton;
AddHandlers();
}
private void AddHandlers()
{
if (control != null)
{
control.MouseDown += new System.Windows.Forms.MouseEventHandler(control_MouseDown);
control.MouseUp += new MouseEventHandler(control_MouseUp);
control.MouseMove += new MouseEventHandler(control_MouseMove);
if (enableDrawing)
control.Paint += new PaintEventHandler(control_Paint);
}
}
private void RemoveHandlers()
{
if (control != null)
{
control.MouseDown -= new System.Windows.Forms.MouseEventHandler(control_MouseDown);
control.MouseUp -= new MouseEventHandler(control_MouseUp);
control.MouseMove -= new MouseEventHandler(control_MouseMove);
if (enableDrawing)
control.Paint -= new PaintEventHandler(control_Paint);
}
}
/// <summary>
/// Feuert, wenn der Benutzer eine in Gestures enthaltene Mausgeste ausführt
/// </summary>
public event EventHandler<GestureEventArgs> GestureRecognized;
private Collection<Gesture> gestures = new Collection<Gesture>();
/// <summary>
/// Die Liste der Gesten, die erkannt werden können
/// </summary>
public Collection<Gesture> Gestures
{
get { return gestures; }
}
private bool enableDrawing = true;
public bool EnableDrawing
{
get { return enableDrawing; }
set
{
if (control != null)
{
bool enable = value && !enableDrawing && (value != enableDrawing);
if (enable)
control.Paint += new PaintEventHandler(control_Paint);
else if (!enable)
control.Paint -= new PaintEventHandler(control_Paint);
enableDrawing = value;
}
}
}
private bool checkGestureOnDrawing = true;
/// <summary>
/// Gibt an, ob die Geste schon während des Zeichnens überprüftwerden soll, oder legt dies fest
/// </summary>
public bool CheckGestureOnDrawing
{
get { return checkGestureOnDrawing; }
set { checkGestureOnDrawing = value; }
}
private System.Windows.Forms.Control control;
/// <summary>
/// Das Steuerelement, das die Gesten entgegennimmt
/// <remarks>
/// Falls das Steuerelement benutzerdefiniert ist, gehen sie sicher, dass die überschriebenen Methoden
/// die Methode der Basisklasse aufrufen, damit die Events an den GestureProvider weitergeleitet werden.
/// Betroffen hiervon sind: OnPaint, OnMouseDown, OnMouseMove und OnMouseUp
/// In der OnPaint-Methode sollte base.onPaint(e) nach dem benutzerdefinierten Zeichnen aufgerufen werden,
/// damit der GestureProvider die Geste auf das Steuerelement zeichnen kann. Außerdem setzt dieser alle
/// Transformationen zurück, damit die Mausgeste an der richtigen Position und mit der richtigen Größe
/// gezeichnet wird.
/// </remarks>
/// </summary>
public System.Windows.Forms.Control Control
{
get { return control; }
set
{
RemoveHandlers();
control = value;
AddHandlers();
}
}
private MouseButtons button;
/// <summary>
/// Die Maustaste, die gedrückt wird, während die Geste ausgeführt wird
/// </summary>
public MouseButtons Button
{
get { return button; }
set { button = value; }
}
private int preciseness = 50;
/// <summary>
/// Gibt den Wert der minimalen Länge eines Segments an oder legt diesen fest
/// Je höher dieser Wert ist, desto länger muss eine Liniengruppe sein, um als Teil der Mausgeste zu gelten
/// </summary>
public int Preciseness
{
get { return preciseness; }
set { preciseness = value; }
}
void control_Paint(object sender, PaintEventArgs e)
{
//Zurücksetzen der Transformationen; Grund: siehe oben bei Property "Control"
e.Graphics.ResetTransform();
Color lineColor;
if (checkGestureOnDrawing)
lineColor = validGesture ? Color.Green : Color.Red;
else
lineColor = Color.Black;
foreach (Line l in this.lines)
e.Graphics.DrawLine(new Pen(lineColor, 5.0f), l.StartPoint, l.EndPoint);
if ((start.X > 0) && (mouseDown))
e.Graphics.DrawLine(new Pen(lineColor, 5.0f), start, mousePosition);
}
void control_MouseMove(object sender, MouseEventArgs e)
{
if (mouseDown)
{
mousePosition = e.Location;
Line l = new Line(start, e.Location);
//Wenn die Linie 20 Pixel lang ist, wird sie der Liste der Linien hinzugefügt
if (Line.GetLength(l.StartPoint, l.EndPoint) > 20)
{
lines.Add(l);
//Überprüfen, ob die geste schon richtig ist
if (CheckGestureOnDrawing)
{
validGesture = false;
foreach (Gesture g in this.Gestures)
if (CheckGesture(g))
validGesture = true;
}
//Der Startpunkt der neuen Linie ist der Endpunkt der alten
start = e.Location;
}
control.Invalidate();
}
}
void control_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (e.Button == button)
mouseDown = true;
mousePosition = e.Location;
start = e.Location;
control.Invalidate();
}
void control_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == this.button)
{
mouseDown = false;
//Der Liste der Linien wird eine Letzte Linie hinzugefügt, auch wenn diese nicht die
//erforderliche Länge hat
lines.Add(new Line(start, e.Location));
start = new Point(-1, -1);
if (GestureRecognized != null)
{
foreach (Gesture gesture in this.Gestures)
{
if (CheckGesture(gesture))
GestureRecognized(this, new GestureEventArgs(gesture));
}
}
control.Invalidate();
//Linien werden gelöscht
lines.Clear();
}
}
/// <summary>
/// Füllt rekursiv eine Liste mit Liniengruppen, die aus der Liste der Linien erzeugt werden.
/// </summary>
/// <param name="lineGroups">Die zu füllende Liste</param>
/// <param name="index">Der Index der ersten Linie der neuen Liniengruppe</param>
/// <returns>Gibt eine Liste von Liniengruppen zurück</returns>
private List<LineGroup> GetLineGroups(List<LineGroup> lineGroups, int index)
{
LineDirections direction = this.lines[index].Direction;
int indx = index;
LineGroup linegroup = new LineGroup();
linegroup.StartPoint = this.lines[indx].StartPoint;
linegroup.Direction = direction;
//wenn die Richtung aufeinanderfolgender Linien gleich ist, gehören sie zu einer Gruppe
//tmp und direction werden verglichen. Unterscheiden sie sich, beginnt eine neue Liniengruppe.
LineDirections tmp = direction;
while ((direction == tmp) && (indx < lines.Count))
{
tmp = this.lines[indx].Direction;
linegroup.EndPoint = this.lines[indx].EndPoint;
indx++;
}
lineGroups.Add(linegroup);
if (indx == lines.Count)
{
//Alle Linien wurden durchlaufen, also Liste zurückgeben
return lineGroups;
}
else
//rekursiver Aufruf der Methode mit Liste und neuem Index als Parameter
return GetLineGroups(lineGroups, indx);
}
/// <summary>
/// Überprüft, ob eine Mausgeste aus der Liste ausgeführt wurde
/// </summary>
/// <param name="gesture">Die Mausgeste, auf die geprüft werden soll</param>
/// <returns></returns>
private bool CheckGesture(Gesture gesture)
{
List<LineGroup> linegroups = this.GetLineGroups(new List<LineGroup>(), 0);
//Überprüfung, ob die Liniengruppe die nötige Länge hat
for (int c = 0; c < linegroups.Count; c++)
{
if (linegroups[c].Length < preciseness)
linegroups.RemoveAt(c);
}
//Gibt es unterschiedlich viele Liniengruppen und Elemente der Geste, können diese nicht
//übereinstimmen
if (linegroups.Count == gesture.GestureDirections.Length)
{
for (int x = 0; x < gesture.GestureDirections.Length; x++)
{
//Stimmen die Richtungen nicht überein, sind die Gesten nicht gleich
if (!(gesture.GestureDirections[x] == linegroups[x].Direction))
return false;
}
}
else
return false;
return true;
}
}
//---------------------------------------------------------------------------------------
/// <summary>
/// Eine Mausgeste, die vom GestureProvider erkannt werden kann
/// </summary>
[Serializable]
public class Gesture
{
/// <summary>
/// Erzeugt eine neue Instanz einer Mausgeste
/// </summary>
/// <param name="gestureDirections">Die Richtungen der Teile der Mausgeste</param>
/// <param name="name">Der Name der Mausgeste. Wird zum unterscheiden mehrerer Gesten benutzt.</param>
public Gesture(LineDirections[] gestureDirections, string name)
{
this.gestureDirections = gestureDirections;
this.name = name;
}
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
private LineDirections[] gestureDirections = new LineDirections[0];
/// <summary>
/// Die Richtungen der Teile der Mausgeste
/// <example>
/// Ein Beispiel für eine Mausgeste wäre ( Start bei (S) und Ende bei (E):
///
/// /|
/// / |
/// / |
/// / |
///(S) (E)
///
/// Als Sourcecode:
/// <code>
/// Gesture g = new Gesture( new LineDirection[] { LineDirection.up | LineDirection.Right, LineDirection.Down } );
/// </code>
/// </example>
/// </summary>
public LineDirections[] GestureDirections
{
get { return gestureDirections; }
set { gestureDirections = value; }
}
}
//---------------------------------------------------------------------------------------
/// <summary>
/// Beschreibt die Richtung einer Linie.
/// Die Werte können mit dem '|' (bitweises ODER) Operator verknüpft werden.
/// "LineDirection.Up | LineDirection.Left" beschreibt eine Linie, die von rechts unten
/// diagonal nach links oben verläuft.
/// Es muss aber darauf geachtet werden, dass keine Widersprüche wie
/// "LineDirection.Up | LineDirection.Down" auftreten. Dies führt dazu, dass die Geste
/// nicht erkannt werden kann.
/// </summary>
[FlagsAttribute]
public enum LineDirections
{
None = 0,
Up = 1,
Down = 2,
Left = 4,
Right = 8
}
//---------------------------------------------------------------------------------------
internal class Line
{
/// <summary>
/// Beschreibt eine Linie, deren Winkel zu einer horizontalen Linie
/// für die Erkennung von Gesten von Bedeutung ist.
/// </summary>
/// <param name="start">Der Startpunkt der Linie</param>
/// <param name="end">Der Endpunkt der Linie</param>
public Line(Point start, Point end)
{
startPoint = start;
endPoint = end;
angle = this.GetAngle();
direction = GetLineDirection();
}
private LineDirections direction;
/// <summary>
/// Gibt die Richtung dieser Linie zurück oder legt diese fest.
/// <seealso cref="LineDirection"/>
/// </summary>
public LineDirections Direction
{
get { return direction; }
}
private double angle;
/// <summary>
/// Gibt den Winkel dieser Linie zu einer horizontalen Linie zurück.
/// </summary>
public double Angle
{
get { return angle; }
}
private Point startPoint;
/// <summary>
/// Gibt den Startpunkt dieser Linie zurück.
/// </summary>
public Point StartPoint
{
get { return startPoint; }
}
private Point endPoint;
/// <summary>
/// Gibt den Endpunkt dieser Linie zurück.
/// </summary>
public Point EndPoint
{
get { return endPoint; }
}
/// <summary>
/// Berechnet die Länge der Linie
/// </summary>
/// <returns>Gibt die Länge der Linie als float Gleitkommazahl zurück</returns>
public static float GetLength(Point start, Point end)
{
Point vector = new Point(end.X - start.X, end.Y - start.Y);
return (float)Math.Sqrt(Math.Pow(vector.X, 2) + Math.Pow(vector.Y, 2));
}
private double GetAngle()
{
//Vektor einer horizontalen Linie
Point normVector = new Point(1, 0);
//Vektor dieser Linie
Point vector = new Point(endPoint.X - startPoint.X, endPoint.Y - startPoint.Y);
/*Um den Winkel zweier Vektoren zu berechnen, benutzt man diese Formel:
* v1 = Vektor 1
* v2 = Vektor 2
* | | = Betrag
*
* |v1 * v2|
* cos alpha = -------------
* |v1| * |v2|
*/
//Der Zähler des Bruches auf der rechten Seite der Formel
double a = vector.X * normVector.X;
if (a < 0)
a *= -1;
//Der Nenner des Bruches auf der rechten Seite der Formel
double b = GetLength(startPoint, endPoint);
//Der gesamte Bruch
double cosin = a / b;
//Berechnung des Winkels im Bogenmaß
double angle = Math.Acos((double)cosin);
//Gibt den Winkel in Grad zurück
return (180 * angle / Math.PI);
}
/// <summary>
/// Bestimmt anhand des Winkels und der Position von Start- und Endpunkt die Richtung der Linie
/// </summary>
/// <returns>Gibt die Richtung der Linie zurück</returns>
private LineDirections GetLineDirection()
{
//Linie ist diagonal
if ((angle > 20) && (angle < 80))
{
if ((startPoint.X < endPoint.X) && (startPoint.Y < endPoint.Y))
return LineDirections.Right | LineDirections.Down;
else if ((startPoint.X > endPoint.X) && (startPoint.Y < endPoint.Y))
return LineDirections.Left | LineDirections.Down;
else if ((startPoint.X < endPoint.X) && (startPoint.Y > endPoint.Y))
return LineDirections.Right | LineDirections.Up;
else if ((startPoint.X > endPoint.X) && (startPoint.Y < endPoint.Y))
return LineDirections.Left | LineDirections.Up;
}
//Linie ist annähernd horizontal
else if (angle <= 20)
{
if (startPoint.X < endPoint.X)
return LineDirections.Right;
else
return LineDirections.Left;
}
//Linie ist annähernd vertikal
else if ((angle < 97) && (angle > 80))
{
if (startPoint.Y < endPoint.Y)
return LineDirections.Down;
else
return LineDirections.Up;
}
//sollte eigentlich nicht vorkommen, aber Methode muss immer einen Wert zurückgeben
return LineDirections.None;
}
}
//---------------------------------------------------------------------------------------
/// <summary>
/// Eine Gruppe von Linien mit der gleichen Richtung.
/// </summary>
internal struct LineGroup
{
private LineDirections direction;
/// <summary>
/// Gibt die Richtung der Liniengruppe zurück oder legt diese fest.
/// <seealso cref="LineDirection"/>
/// </summary>
public LineDirections Direction
{
get { return direction; }
set { direction = value; }
}
/// <summary>
/// Gibt die Länge der Liniengruppe zurück oder legt diese fest.
/// </summary>
public float Length
{
get { return Line.GetLength(startPoint, endPoint); }
}
private System.Drawing.Point startPoint;
/// <summary>
/// Gibt den Startpunkt der Liniengruppe zurück oder legt diesen fest.
/// </summary>
public System.Drawing.Point StartPoint
{
get { return startPoint; }
set { startPoint = value; }
}
private System.Drawing.Point endPoint;
/// <summary>
/// Gibt den Endpunkt der Liniengruppe zurück oder legt diesen fest.
/// </summary>
public System.Drawing.Point EndPoint
{
get { return endPoint; }
set { endPoint = value; }
}
}
//---------------------------------------------------------------------------------------
public class GestureEventArgs : EventArgs
{
private Gesture gesture;
/// <summary>
/// Die Mausgeste
/// </summary>
public Gesture Gesture
{
get { return gesture; }
}
/// <summary>
/// Erzeugt eine neue Instanz von GestureEventArgs
/// </summary>
/// <param name="gesture">Die Mausgeste</param>
public GestureEventArgs(Gesture gesture)
{
this.gesture = gesture;
}
}
}