当前位置:   article > 正文

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

c# 模拟鼠标点击

一.写在前面

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

二.引用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);
  • 1
  • 2
  • 3
  • 4

这里定义声明动态链接库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); 
  • 1
  • 2
  • 3

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);
  • 1
  • 2
  • 3

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);
  • 1
  • 2
  • 3

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  
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
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 = "查询关键字";
            }

            
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

参考资料

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/309814
推荐阅读
相关标签
  

闽ICP备14008679号