Feedback

C# - Mausgesten

Veröffentlicht von am 11/22/2007
(3 Bewertungen)
Hallo,
mit diesem, zugegeben etwas langen Snippet, jedes beliebige Control Mausgesten erkennen.
Hierzu werden 20 px lange Linien gezeichnet, deren Winkel zu einer horizontalen Linie berechnet, dann aus Linien mit ungefähr gleicher Richtung Gruppen gebildet, und diese Gruppe dann mit einem LineDirections Array verglichen.
Will man also zum Beispiel ein 'Z' als Mausgeste für ein Fenster definieren, so schreibt man im Code:

GestureProvider provider;

public MyForm()
{
provider = new GestureProvider(this, MouseButtons.Left);

Gesture gestureZ = new Gesture(
new LineDirections[]{ LineDirections.Right, LineDirections.Left | LineDirections.Down, LineDirections.Right }, "ZGesture");
}

LineDirections ist eine Flags-Enumeration, so können mehrere Richtungen kombiniert werden.
"LineDirections.Left | LineDirections.Down"
bedeutet zum Beispiel "nach links-unten".

Natürlich ist das ganze nicht so schlau wie z.B. ein Neuronales Netz, aber einfache Gesten aus 3 bis 4 Segmenten erkennt es ganz gut,
Rundungen funktionieren aber nicht.
Außerdem wird die Länge der einzelnen Segmente nicht überprüft, so werden auch unproportionale Gesten als richtig erkannt. Diese Überprüfung zu implementieren würde den Rahmen eines Snippets aber sprengen, deshalb hier sozusagen die Basics.
Viel Spaß,
Big Al
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;
        }
    }
}
Abgelegt unter Mausgesten, Maussteuerung.

1 Kommentare zum Snippet

danieljena schrieb am 12/9/2016:
Kann sein, dass es am Freitag liegt. Aber wo definiere ich was bei einer entsprechenden Mausgeste passieren soll?
 

Logge dich ein, um hier zu kommentieren!