Freitag, 1. Juli 2011

Maus steuern

Auf User Anfragen erscheint heute ein Post zum Thema, wie man mit C# die Maus steuern kann, sie also komplett auf dem ganzen Bildschirm bewegen und z.B. Klicks simulieren kann.
Ich habe schon einen Post zum Simulieren von Tastendrücken geschrieben, welches ganz einfach mit der Funktion SendKeys() umgesetzt werden kann.
Die Maus zu steuern ist nicht mehr ganz so einfach, es gibt (noch) keine eingebaute .Net Funktion, wir müssen auf nicht verwaltete System Funktionen zurückgreifen.
Kernstück ist die P/Invoke Funktion SendInput(). Mit dieser Funktion können verschiedene Kommandos an den ausführenden Computer gesendet werden, zum Beispiel direkte Hardwareeingaben, Tastatur- und eben Mausereignisse (im folgenden Inputs genannt).
Die Funktion erwartet 3 Parameter: Der 1. beschreibt die Anzahl der übergebenen Inputs, der 2. enthält eine Referenz auf die tatsächlichen Inputdaten, der 3. speichert die Größe eines einzelnen Inputs.
Den 2. Parameter muss vom Typ einer Struktur sein, welche mindestens den Typ des Inputs (also Hardware, Tastatur, Maus ...) sowie die Daten des Inputs enthält.
Die Struktur habe ich Input genannt.
Mausinputdaten selber haben mehrere Daten, wie die X - und Y - Koordinate, welche ich in der Struktur MouseInput zusammengefasst habe.

Zuerst möchte ich nun das Bewegen der Maus beschreiben: Wir brauchen also eine Instanz der Struktur Input mit den gewünschten Zielmauskoordinaten.
Als Typ des Inputs setzen wir 0, welches ein Mausereignis symbolisiert.
Die Mausdaten repräsentiert durch eine Instanz von MouseInput lassen wir uns über die Funktion CreateMouseInput() erstellen.
Diese erhält alle benötigten Parameter und setzt diese im zurückgegeben Mausereignis.
Zum Bewegen der Maus sind nur die Werte für X und Y wichtig (Zielkoordinaten) sowie für DwFlags. Dieses Feld kann bestimmte Flags aufnehmen, wie zum Beispiel den Druck eines Mausbuttons.
In diesem Fall übergeben wir aber 2 vorher definierte Konstanten (so haben wir leicht zu merkende Namen anstatt Zahlen), MOUSEEVENTF_ABSOLUTE und MOUSEEVENTF_MOVE, um anzuzeigen, dass wir die Maus bewegen wollen. Erstere sagt aus, dass wir ein Mausereignis nicht nur im aktuellen Fenster herbeiführen wollen, sondern auf dem ganzen Bildschirm, zweite zeigt die gewünschte Bewegung der Maus an.

Das Simulieren eines (Links-)Klicks funktioniert ähnlich.
Wir müssen wieder einen Input mit den entsprechenden Daten erstellen, verwenden diesmal aber ein Array der Länge 2, da wir zuerst das Drücken der linken Maustaste und anschließend das Loslassen dieser simulieren wollen.
Über die Funktion CreateMouseInput() lassen wir uns dafür 2 MouseInputs erstellen und übergeben für den Wert von DwFlags die entsprechenden Konstanten.

Ich hoffe, diese kurze Erläuterung sowie der folgende Code helfen:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using System.Runtime.InteropServices;


namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // P/Invoke Funktion u.a. zum Steuern der Maus
        [DllImport("user32.dll", SetLastError = true)]
        private static extern uint SendInput(uint nInputs, Input[] pInputs, int cbSize);

        /// <summary>
       /// Struktur für Mausdaten
        /// </summary>
        struct MouseInput
        {
            public int X; // X - Koordinate
            public int Y; // Y - Koordinate
            public uint MouseData; // Mausdaten, z.B. für Mausrad
            public uint DwFlags; // weitere Mausdaten, z.B. für Mausbuttons
            public uint Time; // Zeit des Events
            public IntPtr DwExtraInfo; // weitere Informationen
        }

        /// <summary>
       /// Oberstruktur für InputDaten der Funktion SendInput
        /// </summary>
        struct Input {
            public int Type; // Typ des Inputs, 0 für Maus  
            public MouseInput Data; // Mausdaten
        }

        // Konstanten für Mausflags
        const uint MOUSEEVENTF_LEFTDOWN = 0x0002; // linken Mausbutton drücken
        const uint MOUSEEVENTF_LEFTUP = 0x0004; // linken Mausbutton loslassen
        const uint MOUSEEVENTF_ABSOLUTE = 0x8000; // ganzen Bildschirm ansprechen, nicht nur aktuelles Fenster
        const uint MOUSEEVENTF_MOVE = 0x0001; // Maus bewegen

        private MouseInput CreateMouseInput(int x, int y, uint data, uint time, uint flag)
        {
            // aus gegebenen Daten Objekt vom Typ MouseInput erstellen, welches dann gesendet werden kann
            MouseInput Result = new MouseInput();
            Result.X = x;
            Result.Y = y;
            Result.MouseData = data;
            Result.Time = time;
            Result.DwFlags = flag;
            return Result;
        }

        private void SimulateMouseClick()
        {
            // Linksklick simulieren: Maustaste drücken und loslassen
            Input[] MouseEvent = new Input[2];
            MouseEvent[0].Type = 0;
            MouseEvent[0].Data = CreateMouseInput(0, 0, 0, 0, MOUSEEVENTF_LEFTDOWN);

            MouseEvent[1].Type = 0; // INPUT_MOUSE;
            MouseEvent[1].Data = CreateMouseInput(0, 0, 0, 0, MOUSEEVENTF_LEFTUP);

            SendInput((uint)MouseEvent.Length, MouseEvent, Marshal.SizeOf(MouseEvent[0].GetType()));
        }

        private void SimulateMouseMove(int x, int y) {
            Input[] MouseEvent = new Input[1];
            MouseEvent[0].Type = 0;
            // Maus bewegen: Flags ABSOLUTE (ganzen Bildschirm verfügbar machen) und MOVE (bewegen)
            MouseEvent[0].Data = CreateMouseInput(x, y, 0, 0, MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE);
            SendInput((uint)MouseEvent.Length, MouseEvent, Marshal.SizeOf(MouseEvent[0].GetType()));
        }


         private void button1_Click(object sender, EventArgs e)
         {
             // Maus testweise bewegen
             SimulateMouseMove(0, 0);
             // und anschließend klicken
             SimulateMouseClick();
         }
    }
}