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();
         }
    }
}

Kommentare:

  1. const uint MOUSEEVENTF_LEFTDOWN = 0x0002; // linken Mausbutton drücken
    const uint MOUSEEVENTF_LEFTUP = 0x0004; // rechten Mausbutton drücken

    Da stimmt was nicht, die 2 Konstante heisst MOUSEEVENTF_LEFTUP = 0x0004 (Also laut Konstantnamen wird hier das heben der linken maustaste simuliert), im Kommentar dazu steht aber "rechten Mausbutton drücken".

    Dies könnte für nicht erfahrene Programmierer verwirrend sein.

    AntwortenLöschen
  2. Danke stimmt!
    Der Fehler ist jetzt korrigiert, 0x0004 entspricht in Wirklichkeit dem Loslassen des linken Mausbuttons.
    Übrigens, auf der sehr guten Seite http://pinvoke.net/default.aspx/user32.sendinput gibt's mehr Informationen zu der sendinput() Funktion, dort kann man auch weitere Konstanten nachschlagen.

    AntwortenLöschen
  3. Hat mir sehr geholfen! Mach weiter so!

    AntwortenLöschen
  4. Was ist los Oliver? Gibt's nix mehr?

    AntwortenLöschen
  5. Hey kannst du mal was über XNA. Gamestudios was machen?

    AntwortenLöschen
  6. Hey,
    doch natürlich möchte ich noch weiter machen, leider habe ich im Moment sehr wenig Zeit, tut mir leid
    Ich hoffe die bis jetzt verfügbaren Posts reichen doch aus um ein paar Fragen zu beantworten und Anreize zu geben!

    AntwortenLöschen
  7. Bei mir Funktioniert der MOUSEMOVE nicht ganz! Die Maus bewegt sich nicht egal was ich für eine Zahl eingebe!

    AntwortenLöschen
  8. Hi,

    kommt denn ein Fehler?
    Ich würde mal auf einen Fehler in Verbindung mit der DLL tippen.
    Fehler hierbei werden aber nicht ausgegeben, du kannst sie aber mit System.Runtime.InteropServices.Marshal.GetLastWin32Error() auslesen.
    Bringt das was?

    Grüße
    Oliver

    AntwortenLöschen
  9. Hi,

    wie kann ich simulieren das er mit der rechtenmaus klickt?

    lg

    ps.: Sonst super GUT

    AntwortenLöschen
    Antworten
    1. Hallo,
      danke!
      Für die rechte Maustaste ersetze einfach obige Konstanten für die linke Maustaste durch:

      const uint MOUSEEVENTF_RIGHTDOWN = 0x0008,
      const uint MOUSEEVENTF_RIGHTUP = 0x0010,

      Löschen
  10. Hallo Oliver,

    bei mir funktioniert das bewegen der Maus ebenfalls nicht. Wo muss ich "System.Runtime.InteropServices.Marshal.GetLastWin32Error()" schreiben, dass ich eventuelle Fehlermeldungen ausgegeben bekomme?

    AntwortenLöschen
    Antworten
    1. Hallo, wieso funktioniert es nicht?
      System.Runtime.InteropServices.Marshal.GetLastWin32Error() gibt dir den letzten aufgetretenen Fehler aus, deshalb ist dieser Aufruf nach der Simulation des Mausereignisses zu platzieren, z.B. also nach SimulateMouseMove() oder SimulateMouseClick().

      Löschen
  11. Hallo,
    ist zwar schon eine Weile her der Post, aber vielleicht kann mir ja trotzdem jemand weiterhelfen...
    Wie kann ich simulieren, dass mit gehaltener Ctrl-Taste geklickt wird bzw. Simulieren, dass während des Klicks die Ctrl-Taste gehalten wurde?
    Danke schonmal :-)

    AntwortenLöschen