Friday, July 1, 2011

Control the Mouse

Due to readers' requests I today publish a post about the topic, how to control the mouse using C#, meaning to move the coursor over the whole screen and to simulate clicks for example.
I already wrote a post about simulating key presses, which can easy implemented with the function SendKeys().
Controlling the mouse is not that easy, there is (yet) no own .Net function, we have to use P/Invoke functions. Core is the function SendInput(). With this function different commands can be send to the executing computer, for example hardware inputs, keyboard and mouse events (called inputs in the following).
The function expects 3 arguments: The first describes the number of given inputs, the second a reference to the actual input data and the third saves the size of each input. The second parameter has to be a structure which contains at least the type of the input (so hardware, keyboard, mouse ...) aswell as the data of the input.
I called this structure input. Mouse input data have multiple data, as the X- and Y - coordinate, which I summarized in the structure MouseInputI.

Now first I want to describe moving the mouse: We need therefor an instance of the structure Input with the desired target coordinates.
As type of the input we set a 0, which describes a mouse event. The mouse data represented by an instance of MouseInput we create via the function CreateMouseInput(). This gets all needed parameters and sets them in the corresponding mouse result. For moving the mouse only the values for X and Y (target coordinates) are important aswell as for DwFlags. This array describes certain flags, as for example the pressing of a mouse button. In this case we set 2 previously defined constants, MOUSEEVENTF_ABSOLUTE and MOUSEEVENTF_MOVE, to indicate, that we want to move the mouse. The first says, that we want to create a mouse event on the whole screen and not only on our application window, the second indicates the desired moving of the mouse.

The simulation of a click works similiarily. We first have to create an input again with the desired data, this time though we use an array of length 2, as we want to simulate the pressing and concluding letting go of the mouse button.
With the function CreateMouseInput() we therefor create 2 mouse inputs and set the right constants for the DwFlags.
I hope, this explanation as well as the following code makes the topic clear:

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 function for controlling the mouse
        [DllImport("user32.dll", SetLastError = true)]
        private static extern uint SendInput(uint nInputs, Input[] pInputs, int cbSize);

        /// <summary>
       /// structure for mouse data
        /// </summary>
        struct MouseInput
        {
            public int X; // X coordinate
            public int Y; // Y coordinate
            public uint MouseData; // mouse data, e.g. for mouse wheel
            public uint DwFlags; // further mouse data, e.g. for mouse buttons
            public uint Time; // time of the event
            public IntPtr DwExtraInfo; // further information
        }

        /// <summary>
       /// super structure for input data of the function SendInput
        /// </summary>
        struct Input {
            public int Type; // type of the input, 0 for mouse  
            public MouseInput Data; // mouse data
        }

        // constants for mouse flags
        const uint MOUSEEVENTF_LEFTDOWN = 0x0002; // press left mouse button
        const uint MOUSEEVENTF_LEFTUP = 0x0004; // release left mouse button
        const uint MOUSEEVENTF_ABSOLUTE = 0x8000; // whole screen, not just application window
        const uint MOUSEEVENTF_MOVE = 0x0001; // move mouse

        private MouseInput CreateMouseInput(int x, int y, uint data, uint time, uint flag)
        {
            // create from the given data an object of the type MouseInput, which then can be send
            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;
            // move mouse: Flags ABSOLUTE (whole screen) and MOVE (move)
            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)
         {
             // move mouse
             SimulateMouseMove(0, 0);
             // click
             SimulateMouseClick();
         }
    }
}

2 comments:

  1. Hi trying to get your code to work properly and i'm getting some weird behavior compared to using this instead:
    [DllImport("user32.dll")]
    static extern bool SetCursorPos(int X, int Y);
    The positoning is off by quite a bit.
    If i disable absolute positioning from your code x position is correct but y is still off, any idea of a fix ?

    ReplyDelete
  2. Ok figured it out here for any that want to use and get correct screen positions add:

    enum SystemMetric
    {
    SM_CXSCREEN = 0,
    SM_CYSCREEN = 1,
    }

    [DllImport("user32.dll")]
    static extern int GetSystemMetrics(SystemMetric smIndex);

    int CalculateAbsoluteCoordinateX(int x)
    {
    return (x * 65536) / GetSystemMetrics(SystemMetric.SM_CXSCREEN);
    }

    int CalculateAbsoluteCoordinateY(int y)
    {
    return (y * 65536) / GetSystemMetrics(SystemMetric.SM_CYSCREEN);
    }


    And use CalculateAbsoluteCoordinateX(x) and CalculateAbsoluteCoordinateY(y) to get the correct screen positions.

    ReplyDelete