一.写在前面
作为一个人力资源工作者,会经常遇到填表、报表的事务,其实有时候就是重复再重复的点击鼠标工作,特别是遇到一些复杂的客户端程序、网页程序,诸如用友客户端、社保管理系统等等,就尤其让人头疼。正好这段时间做了很多这方面的工作,搜索了不少的资料,为了转化学习效果,记录于此,温故知新。
二.引用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.Zero
(IntPtr
用于表示指针或句柄的特定类型(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#模拟鼠标和键盘操作