在另一个窗口内编程实现鼠标点击

32

是否可以在不将鼠标移动到该位置的情况下,在另一个窗口中以编程方式单击位置,即使该窗口不在顶部也是如此?我想向另一个窗口发送一种消息,以模拟鼠标单击位置。

我尝试使用PostMessage来实现这个问题:

PostMessage(WindowHandle, 0x201, IntPtr.Zero, CreateLParam(300,300));
PostMessage(WindowHandle, 0x202, IntPtr.Zero, CreateLParam(300,300));

我这样实现了CreateLParam函数:

private static IntPtr CreateLParam(int LoWord, int HiWord)
{
     return (IntPtr)((HiWord << 16) | (LoWord & 0xffff));
}

问题在于窗口被锁定在它的位置上。我认为我的应用程序点击了(1,1)坐标。有人能帮我解决这个问题吗?

编辑: 这是PostMessage:

[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll")]
public static extern bool PostMessage(IntPtr WindowHandle, int Msg, IntPtr wParam, IntPtr lParam);

0x201和0x202分别代表了鼠标左键按下和释放的消息WM_LBUTTONDOWN和WM_LBUTTONUP。


这是你控制的另一个窗口吗?如果不是,那么这似乎是一个非常奇怪的请求。 - Tejs
你想要点击哪个程序?有些程序(主要是游戏)有机制来处理你所尝试的操作并忽略它。在这种情况下,你最好的机会是使用WinApi将游戏置于最顶层,移动鼠标,点击,然后将鼠标移回原位,再将游戏移回先前的Z顺序。 - SimpleVar
另外,在执行 << 16 操作之前,尝试将 HiWord 强制转换为 uint - SimpleVar
3个回答

36
你不能通过发送消息来实现,而是要使用 SendInput Windows API。 调用 ClickOnPoint 方法,这是一个来自表单点击事件的示例,所以 this.handle 是表单句柄。请注意,这些是发送句柄的窗口上的客户端坐标,你可以轻松更改它并发送屏幕坐标,在这种情况下,你不需要 handle 或下面的 ClientToScreen 调用。
ClickOnPoint(this.Handle, new Point(375, 340));

更新:现在使用SendInput,谢谢Tom。

顺便说一下,我只使用了这个示例所需的声明,如果需要更多内容,可以使用一个很好的库:Windows Input Simulator(C# SendInput包装器-模拟键盘和鼠标)

  public class ClickOnPointTool
  {

    [DllImport("user32.dll")]
    static extern bool ClientToScreen(IntPtr hWnd, ref Point lpPoint);

    [DllImport("user32.dll")]
    internal static extern uint SendInput(uint nInputs, [MarshalAs(UnmanagedType.LPArray), In] INPUT[] pInputs,  int cbSize);

#pragma warning disable 649
    internal struct INPUT
    {
      public UInt32 Type;
      public MOUSEKEYBDHARDWAREINPUT Data;
    }

    [StructLayout(LayoutKind.Explicit)]
    internal struct MOUSEKEYBDHARDWAREINPUT
    {
      [FieldOffset(0)]
      public MOUSEINPUT Mouse;
    }

    internal struct MOUSEINPUT
    {
      public Int32 X;
      public Int32 Y;
      public UInt32 MouseData;
      public UInt32 Flags;
      public UInt32 Time;
      public IntPtr ExtraInfo;
    }

#pragma warning restore 649


    public static void ClickOnPoint(IntPtr wndHandle , Point clientPoint)
    {
      var oldPos = Cursor.Position;

      /// get screen coordinates
      ClientToScreen(wndHandle, ref clientPoint);

      /// set cursor on coords, and press mouse
      Cursor.Position = new Point(clientPoint.X, clientPoint.Y);

      var inputMouseDown = new INPUT();
      inputMouseDown.Type = 0; /// input type mouse
      inputMouseDown.Data.Mouse.Flags = 0x0002; /// left button down

      var inputMouseUp = new INPUT();
      inputMouseUp.Type = 0; /// input type mouse
      inputMouseUp.Data.Mouse.Flags = 0x0004; /// left button up

      var inputs = new INPUT[] { inputMouseDown, inputMouseUp };
      SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT)));

      /// return mouse 
      Cursor.Position = oldPos;
    }

  }

1
System.Drawing(http://msdn.microsoft.com/en-us/library/system.drawing.point.aspx) - Antonio Bakula
4
有没有不用 SetCursorPos 或者不动鼠标的其他方法?我在搜索时几乎看到了相同的非回答... - Jet
2
请注意,您可以使用Cursor.Position而不是GetCursorPos和SetCursorPos。这也消除了POINT结构的需要。 - zastrowm
26
如果目标窗口不在最前面,这种方法无法使用。因为它是在屏幕上点击,而不是窗口内部点击。在问题中明确要求“即使窗口不在最前面也能运行”。 - xbtsw
2
mouse_event 已经很久以前就已经被弃用了,现在应该使用 SendInput。 - Tom
显示剩余5条评论

20

过去,我找到了一种向Windows Media Player发送消息的方法,因此我使用它来模拟我想要的应用程序中的点击!

使用这个类(下面的代码)来查找窗口并发送您想要的消息!

using System;
using System.Runtime.InteropServices;

namespace Mouse_Click_Simulator
{
    /// <summary>
    /// Summary description for Win32.
    /// </summary>
    public class Win32
    {
        // The WM_COMMAND message is sent when the user selects a command item from 
        // a menu, when a control sends a notification message to its parent window, 
        // or when an accelerator keystroke is translated.
        public const int WM_KEYDOWN = 0x100;
        public const int WM_KEYUP = 0x101;
        public const int WM_COMMAND = 0x111;
        public const int WM_LBUTTONDOWN = 0x201;
        public const int WM_LBUTTONUP = 0x202;
        public const int WM_LBUTTONDBLCLK = 0x203;
        public const int WM_RBUTTONDOWN = 0x204;
        public const int WM_RBUTTONUP = 0x205;
        public const int WM_RBUTTONDBLCLK = 0x206;

        // The FindWindow function retrieves a handle to the top-level window whose
        // class name and window name match the specified strings.
        // This function does not search child windows.
        // This function does not perform a case-sensitive search.
        [DllImport("User32.dll")]
        public static extern int FindWindow(string strClassName, string strWindowName);

        // The FindWindowEx function retrieves a handle to a window whose class name 
        // and window name match the specified strings.
        // The function searches child windows, beginning with the one following the
        // specified child window.
        // This function does not perform a case-sensitive search.
        [DllImport("User32.dll")]
        public static extern int FindWindowEx(
            int hwndParent, 
            int hwndChildAfter, 
            string strClassName, 
            string strWindowName);


        // The SendMessage function sends the specified message to a window or windows. 
        // It calls the window procedure for the specified window and does not return
        // until the window procedure has processed the message. 
        [DllImport("User32.dll")]
        public static extern Int32 SendMessage(
            int hWnd,               // handle to destination window
            int Msg,                // message
            int wParam,             // first message parameter
            [MarshalAs(UnmanagedType.LPStr)] string lParam); // second message parameter

        [DllImport("User32.dll")]
        public static extern Int32 SendMessage(
            int hWnd,               // handle to destination window
            int Msg,                // message
            int wParam,             // first message parameter
            int lParam);            // second message parameter
    }
}

例如:

 Win32.SendMessage(iHandle, Win32.WM_LBUTTONDOWN, 0x00000001, 0x1E5025B);

这是我创建的应用程序源代码,可以自动点击“BlueStacks”应用程序,并在特定间隔内执行!

关于FindWindowwParamlParam等问题,您可以随时询问我如何操作!这并不太难 :) ;) 希望能帮到您! :)


看起来,鼠标点击是被发布的,而不是被发送的。PostMessage会是一个更好的解决方案。至少Spy++告诉我点击是被发布的。 - Tom
来这里是为了尝试向BlueStacks发送点击,没有失望! - C4F
4
如何提供点击的坐标?我需要在特定位置进行点击,而不仅仅是在未知位置点击。 - Kosmo零
5
请使用以下代码进行翻译:var w = (y << 16) | x; Win32.SendMessage(iHandle, Win32.WM_LBUTTONDOWN, 0x00000001, w);将变量 y 左移 16 位,然后与变量 x 进行按位或操作,将结果赋给变量 w;最后通过 Win32.SendMessage 函数向 iHandle 窗口发送一个鼠标左键按下的消息,并传递参数 0x00000001 和变量 w。 - M. Jahedbozorgan

3

我无法添加评论 :D

对我而言可以工作:

[DllImport("User32.dll")]
    public static extern Int32 SendMessage(
    int hWnd,               
    int Msg,                
    int wParam,            
    IntPtr lParam);

并将 lParam 中的 coord 结合起来,像这样:

private static IntPtr CreateLParam(int LoWord, int HiWord)
    {
        return (IntPtr)((HiWord << 16) | (LoWord & 0xffff));
    }

并使用:

Clicktoapp.SendMessage(0x00040156, Clicktoapp.WM_LBUTTONDOWN, 0x00000001, CreateLParam(150,150));
        Clicktoapp.SendMessage(0x00040156, Clicktoapp.WM_LBUTTONUP, 0x00000000, CreateLParam(150, 150));

0x00040156 - 窗口句柄。

我在使用spy++查找窗口句柄,但每次都是新的,所以最好使用FindWindow。

P.S

即使窗口不在最上层,也可以截取应用程序窗口的屏幕截图。

https://dev59.com/xnNA5IYBdhLWcg3wn_Q1#911225

我使用这个解决方案。效果很好。


"Clicktoapp.WM_LBUTTONDOWN"和CreateLParam(int LoWord, int HiWord)属性是什么? - Dmitrii Matunin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接