【向重复工作说不】c#之模拟鼠标操作

本文介绍如何使用C#和Windows API进行鼠标模拟,包括SetCursorPos、mouse_event、FindWindow和FindWindowEx等函数,以实现对复杂客户端程序的自动化操作,如在记事本或计算器等软件中点击、输入等任务。
摘要由CSDN通过智能技术生成

一.写在前面

作为一个人力资源工作者,会经常遇到填表、报表的事务,其实有时候就是重复再重复的点击鼠标工作,特别是遇到一些复杂的客户端程序、网页程序,诸如用友客户端、社保管理系统等等,就尤其让人头疼。正好这段时间做了很多这方面的工作,搜索了不少的资料,为了转化学习效果,记录于此,温故知新。

二.引用Windows API

c#模拟鼠标操作,就必须和WindowsAPI打交道,通过引用它内部的几个函数,从而实现在屏幕的指定位置单击、双击,或者对指定的窗体(能够获得句柄的)、控件进行相关控制操作。相关函数如下:

SetCursorPos(设置鼠标位置) mouse_event(控制鼠标动作)
FindWindow(获得窗口的句柄) FindWindowEx(获得子窗口或控件的句柄)

SetCursorPos设置鼠标位置
//设置鼠标位置
[DllImport("user32.dll")] //DllImpor针对非托管的。非托管指的是不利用.net 生成的DLL
//声明一个外部实现方法SetCursorPos()
public static extern bool SetCursorPos(int X, int Y);

这里定义声明动态链接库user32.dll作为静态入口点。SetCursorPos是这个动态链接库里面的内部方法,所以这里不要试图改变大小写什么的。这里定义的方法使用 extern 修饰符意味着该方法在 C# 代码外部实现。extern 修饰符的常见用法是在使用 Interop 服务调入非托管代码时与DllImport 特性一起使用。在这种情况下,还必须将方法声明为static

mouse_event控制鼠标动作
//控制鼠标动作
[DllImport("user32.dll")]
public static extern void mouse_event(MouseEventFlag flags, int dx, int dy, uint data, UIntPtr extraInfo); 

MouseEventFlag 继承uint(uint型为无符号32位整数,占4个字节,取值范围在0~4,294,967,295之间。)的枚举,指代一组鼠标动作标志位集;
dx指鼠标沿x轴绝对位置或上次鼠标事件位置产生以来移动的像素数量;dy指沿y轴的绝对位置或从上次鼠标事件以来移动的像素数量;
data变量是指,如果flags为MOUSE_WHEEL,则该变量指定鼠标轮移动的数量。正值表明鼠标轮向前转动,即远离用户的方向;负值表明鼠标轮向后转动,即朝向用户。一个轮击定义为WHEEL_DELTA,即120。如果flags不是MOUSE_WHEEL,则data应为零;
extraInfo指定与鼠标事件相关的附加32位值,应用程序调用函数GetMessageExtraInfo来获得此附加信息。一般的情况下赋值IntPtr.ZeroIntPtr用于表示指针或句柄的特定类型(A platform-specific type that is used to represent a pointer or a handle.)它被设计成整数,其大小适用于特定平台。它们主要用于本机资源,如窗口句柄)

FindWindow获得窗口的句柄
//在窗口列表中寻找与指定条件相符的第一个窗口,并返回句柄值。这个函数不能查找子窗口。
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

lpClassName是窗口的类名, lpWindowName是窗口的标题。这两个变量可以用Spy++软件来获得。
在搜索的时候不一定两者都知道,但至少要知道其中的一个(不知道的可以赋值null)。有的窗口的标题是比较容易得到的,如"计算器",所以搜索时应使用标题进行搜索。但有的软件的标题不是固定的,如"记事本",如果打开的文件不同,窗口标题也不同,这时使用窗口类搜索就比较方便。如果找到了满足条件的窗口,这个函数返回该窗口的句柄,否则返回0。如果查找子窗口需要用FindWindowEx

FindWindowEx获得窗口或者控件的句柄
//该函数获得一个窗口的句柄,该窗口的类名和窗口名与给定的字符串相匹配。这个函数查找子窗口,从排在给定的子窗口后面的下一个子窗口开始。在查找时不区分大小写。
[DllImport("user32.dll", EntryPoint = "FindWindowEx", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

hwndParent,父窗口句柄,如果hwndParent为 0 ,则函数以桌面窗口为父窗口,查找桌面窗口的所有子窗口。
hwndChildAfter,子窗口句柄,查找从在Z序中的下一个子窗口开始。子窗口必须为hwndParent窗口的直接子窗口而非后代窗口。如果HwndChildAfter为NULL,查找从hwndParent的第一个子窗口开始。如果hwndParent 和 hwndChildAfter同时为NULL,则函数查找所有的顶层窗口及消息窗口。
lpszClass ,窗口或控件类名;lpszWindow ,窗口或控件标题。如果该参数为 NULL,则为所有窗口全匹配。

三.应用

1 首先新建一个类-类名WindowApi
using System;
using System.Runtime.InteropServices;//需要引用,从而使相应的类或者方法来支持托管/非托管模块间的互相调用

namespace 模拟输入
{
    public static class WindowApi
    {
        #region 鼠标操作
        //首先定义一个枚举,其继承uint。这样可以直观的体现鼠标的各类动作。
        //[Flags]位标志属性,从而使该枚举类型的实例可以存储枚举列表中定义值的任意组合。可以用 与(&)、或(|)、异或(^)进行相应的运算。
        [Flags]
        public enum MouseEventFlag : uint //设置鼠标动作的键值
        {
            Move = 0x0001,               //发生移动
            LeftDown = 0x0002,           //鼠标按下左键
            LeftUp = 0x0004,             //鼠标松开左键
            RightDown = 0x0008,          //鼠标按下右键
            RightUp = 0x0010,            //鼠标松开右键
            MiddleDown = 0x0020,         //鼠标按下中键
            MiddleUp = 0x0040,           //鼠标松开中键
            XDown = 0x0080,
            XUp = 0x0100,
            Wheel = 0x0800,              //鼠标轮被移动
            VirtualDesk = 0x4000,        //虚拟桌面
            Absolute = 0x8000
        }
        //设置鼠标位置
        [DllImport("user32.dll")]
        public static extern bool SetCursorPos(int X, int Y);

        //设置鼠标按键和动作
        [DllImport("user32.dll")]
        public static extern void mouse_event(MouseEventFlag flags, int dx, int dy, uint data, UIntPtr extraInfo);
        
        //方法:鼠标左键单击操作:鼠标左键按下和松开两个事件的组合即一次单击
        public static void MouseLeftClickEvent(int dx, int dy, uint data)
        {
            SetCursorPos(dx, dy);
            System.Threading.Thread.Sleep(2 * 1000);
            mouse_event(MouseEventFlag.LeftDown|MouseEventFlag.LeftUp, dx, dy, data, UIntPtr.Zero);
        }
         //方法:鼠标右键单击操作:鼠标右键键按下和松开两个事件的组合即一次单击
        public static void MouseRightClickEvent(int dx, int dy, uint data)
        {
            SetCursorPos(dx, dy);
            System.Threading.Thread.Sleep(2 * 1000);
            mouse_event(MouseEventFlag.RightDown|MouseEventFlag.RigthtUp, dx, dy, data, UIntPtr.Zero);
        }

        #endregion

        #region 句柄函数
        
        //获得窗口的句柄
        [DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
        
        //获得子窗口、子控件的句柄;需要提前知道父窗体的句柄,以及窗口的类名或者标题名。
        [DllImport("user32.dll", EntryPoint = "FindWindowEx", SetLastError = true)]
        public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

        //该函数返回指定窗口的边框矩形的尺寸。该尺寸以相对于屏幕坐标左上角的屏幕坐标给出
        [DllImport("user32.dll")]
        public static extern bool GetWindowRect(IntPtr hwnd, out NativeRECT rect);
        
       


       #endregion  
    }
}
2 在WinForm中使用刚才新建的WindowApi

假如:有这么一个程序,我们要进行这样的操作“ 点击鼠标到查询文本框,输入查询关键字,点击查询按钮,获得查询的内容,然后在点击窗体上的打印按钮,调出win系统的打印对话框(输出PDF),输入PDF的文件名,最后打印输出。这样的动作循环若干次。 ”因为无法获得这个程序里面查询文本框控件的句柄,我们就必须模拟鼠标的操作,点中这个查询框,然后用SendKeys.SendWait("待查询关键字"),发送给定的内容,然后再用鼠标点击这个查询按钮。

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

        private void BtnOutPut_Click(object sender, EventArgs e)
        {
            string str="查询关键字";
            Thread.Sleep(1000);
            for (int i = 0; i < 10; i++)
            {
                str += i;
                Clipboard.SetText(str);

                WindowApi.MouseLeftClickEvent(800, 160, 0);//将鼠标定位到文本框,假设文本框的中间位置的坐标是800,160
                //由于SendKeys.SendWait不能发送中文字符,所以只能用复制粘贴的方式折中。
                SendKeys.SendWait("^A");//全选
                SendKeys.SendWait("^V");//将剪贴板的内容粘贴。
                
                Thread.Sleep(1000);
                WindowApi.MouseLeftClickEvent(950, 160, 0);//将鼠标点击查询按钮
                WindowApi.MouseLeftClickEvent(1000, 300, 0);//将鼠标点击打印按钮
                //根据句柄找到打印窗体
                IntPtr ptrTaskbar = WindowApi.FindWindow(null, "打印设置");
                if (ptrTaskbar != IntPtr.Zero)
                {
                    IntPtr ptrOKBtn = WindowApi.FindWindowEx(ptrTaskbar, IntPtr.Zero, "TButton", "确认");//找到打印窗体中的确定按钮并发送确认信息。
                    WindowApi.GetWindowRect(ptrOKBtn, out WindowApi.NativeRECT rect);
                    this.textControlPos.Text = rect.bottom.ToString() + "--" + rect.left.ToString();
                    WindowApi.SetCursorPos(rect.left + 40, rect.bottom - 15);//将鼠标定位到打印按钮
                                                                             //Thread.Sleep(1000);//delay 5m
                    WindowApi.mouse_event(WindowApi.MouseEventFlag.LeftDown, 0, 0, 0, UIntPtr.Zero);//单击鼠标左键
                    WindowApi.mouse_event(WindowApi.MouseEventFlag.LeftUp, 0, 0, 0, UIntPtr.Zero);//单击鼠标左键
                    Thread.Sleep(5000);
                    //定位打印输出窗体
                    IntPtr ptrPrintOutputForm = WindowApi.FindWindow(null, "将打印输出另存为");
                    IntPtr ptrPrintOutputForm_Edit = WindowApi.FindWindowEx(ptrPrintOutputForm, "Edit", true);//。
                    this.textControlPos.Text = ptrPrintOutputForm_Edit.ToString() + "----" + ptrPrintOutputForm.ToString();
                    WindowApi.SendMessage(ptrPrintOutputForm_Edit, 0x000C, null,str);//输入另存为的文件名
                    IntPtr ptrPrintOutputForm_Save = WindowApi.FindWindowEx(ptrPrintOutputForm, IntPtr.Zero, "Button", "保存(&S)");
                    WindowApi.SendMessage(ptrPrintOutputForm_Save, 0xF5, 0, 0);//保存
                }
                else
                {
                    MessageBox.Show("未能找到打印窗体");
                }

                str = "查询关键字";
            }

            
        }

参考资料

C# 模拟鼠标移动与点击
C# 系统应用之鼠标模拟技术及自动操作鼠标
C#应用WindowsApi实现查找\枚举(FindWindow、EnumChildWindows)窗体控件,并发送消息。
c#里FindWindow的用法
C#模拟鼠标和键盘操作

WinAPI-Wrapper 模拟鼠标点击 用于模拟鼠标移动、点击、窗口操作等的Windows API包装器类。 API 下面是一些可用的方法的总结。有更多的方法和类,比下面列出的要多,但目的是要大致了解包装器能做什么。要查看关于特定方法的详细信息和参数的详细信息,请查看代码本身,因为它的注释很好。 Mouse.cs public static void LeftClick(); public static void RightClick(); public static void MiddleClick(); public static void LeftDown(); public static void LeftUp(); public static void RightDown(); public static void RightUp(); public static void MiddleDown(); public static void MiddleUp(); public static void Move(int x, int y); public static void LeftDrag(Point point1, Point point2, int interval, int lag); Window.cs public static bool DoesExist(string windowTitle); public static IntPtr Get(string windowTitle); public static IntPtr GetFocused(); public static void SetFocused(IntPtr hWnd); public static bool IsFocused(IntPtr hWnd); public static void Move(IntPtr hWnd, int x, int y); public static void Resize(IntPtr hWnd, int width, int height); public static void Hide(IntPtr hWnd); public static void Show(IntPtr hWnd); public static Rectangle GetDimensions(IntPtr hWnd); public static Size GetSize(IntPtr hWnd); public static Point GetLocation(IntPtr hWnd); public static string GetTitle(IntPtr hWnd); public static void SetTitle(IntPtr hWnd, string title); public static void Maximize(IntPtr hWnd); public static void Minimize(IntPtr hWnd); public static void Normalize(IntPtr hWnd); public static Bitmap Screenshot(IntPtr hWnd); public static void RemoveMenu(IntPtr hWnd); public static void Close(IntPtr hWnd); public static void DisableCloseButton(IntPtr hWnd); public static void DisableMaximizeButton(IntPtr hWnd); public static void DisableMinimizeButton(IntPtr hWnd); public static void EnableMouseTransparency(IntPtr hWnd); public static Point ConvertToWindowCoordinates(IntPtr hWnd, int x, int y); public static Point GetCoordinateRelativeToWindow(IntPtr hWnd); Desktop.cs public static Bitmap Screenshot(); public static void HideTaskBar(); public static void ShowTaskBar(); public static int GetWidth(); public static int GetHeight(); 使用 在windows api文件夹中编译代码会产生一个.dll文件。任何引用这个.dll的ccode都可以使用包装器。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值