之前的篇章里面,我们讲过了虚拟键盘的使用,本篇补充一下虚拟鼠标的使用。
虚拟鼠标在不同的使用环境下,需要使用不同的实现方法。如在模拟鼠标移动时,我们会存在两种情况:
- 模拟鼠标移动,真实鼠标并不做移动
- 模拟鼠标移动,鼠标也要跟随移动
在传统的win32应用中,可以有以下两种方式实现:
- 使用
mouse_event
方法,实现模拟硬件鼠标操作 - 使用
SendMessage
方法,通过给指定的窗口或控件句柄发送消息,模拟鼠标操作
二者区别
mouse_event
鼠标,键盘等外设接入主机,实际就相当于接入了IO设备。而mouse_event事件,则不是简单的发消息,它是向I/O队列插入鼠标事件;所以该事件可以模拟硬件的具体的各种操作。
例如:我们在通过mouse_event
移动鼠标时,鼠标的Cursor图标也会跟着做偏移,跟我们直接操作鼠标的效果是一致的。
然而对于具体的窗口或者控件的事件的触发,就必须要求在模拟操作之前,需要触发的控件或者窗口需要先捕获焦点。
具体实现方法如下:
//引用user32.dll,指定 mouse_event 的方法
[DllImport("user32.dll")]
private static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);
//模拟鼠标点击
public static void MouseDown(int x, int y)
{
//模拟鼠标按下操作
mouse_event(MouseMessage.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0);
}
//相关动作枚举值
public enum MouseMessage
{
//移动鼠标
MOUSEEVENTF_MOVE = 0x0001,
//模拟鼠标左键按下
MOUSEEVENTF_LEFTDOWN = 0x0002,
//模拟鼠标左键抬起
MOUSEEVENTF_LEFTUP = 0x0004,
//模拟鼠标右键按下
MOUSEEVENTF_RIGHTDOWN = 0x0008,
//模拟鼠标右键抬起
MOUSEEVENTF_RIGHTUP = 0x0010,
//模拟鼠标中键按下
MOUSEEVENTF_MIDDLEDOWN = 0x0020,
//模拟鼠标中键抬起
MOUSEEVENTF_MIDDLEUP = 0x0040,
//标示是否采用绝对坐标
MOUSEEVENTF_ABSOLUTE = 0x8000,
}
参数说明:
dx:指定鼠标沿x轴的绝对位置或者从上次鼠标事件产生以来移动的数量,依赖于MOUSEEVENTF_ABSOLUTE的设置。给出的绝对数据作为鼠标的实际X坐标;给出的相对数据作为移动的mickeys数。一个mickey表示鼠标移动的数量,表明鼠标已经移动。
dy:指定鼠标沿y轴的绝对位置或者从上次鼠标事件产生以来移动的数量,依赖于MOUSEEVENTF_ABSOLUTE的设置。给出的绝对数据作为鼠标的实际y坐标,给出的相对数据作为移动的mickeys数。
dwData:如果dwFlags为MOUSEEVENTF_WHEEL,则dwData指定鼠标轮移动的数量。正值表明鼠标轮向前转动,即远离用户的方向;负值表明鼠标轮向后转动,即朝向用户。一个轮击定义为WHEEL_DELTA,即120。
如果dwFlagsS不是MOUSEEVENTF_WHEEL,则dWData应为零。
dwExtralnfo:指定与鼠标事件相关的附加32位值。应用程序调用函数GetMessageExtraInfo来获得此附加信息。
返回值:无。
程序中我们直接调用mouse_event函数就可以了 mouse_event(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, 500, 500, 0, 0);
1、这里是鼠标左键按下和松开两个事件的组合即一次单击: mouse_event (MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0 )
2、模拟鼠标右键单击事件: mouse_event (MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0 )
3、两次连续的鼠标左键单击事件 构成一次鼠标双击事件: mouse_event (MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0 ) mouse_event (MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0 )
4、使用绝对坐标 MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, 500, 500, 0, 0
需要说明的是,如果没有使用MOUSEEVENTF_ABSOLUTE,函数默认的是相对于鼠标当前位置的点,如果dx,和dy,用0,0表示,这函数认为是当前鼠标所在的点。5、直接设定绝对坐标并单击 mouse_event(MOUSEEVENTF_LEFTDOWN, X * 65536 / 1024, Y * 65536 / 768, 0, 0); mouse_event(MOUSEEVENTF_LEFTUP, X * 65536 / 1024, Y * 65536 / 768, 0, 0); 其中X,Y分别是你要点击的点的横坐标和纵坐标
sendmessage
通过PostMessage或者SendMessage发送消息的方式实现的模拟动作,实际上只是简单调用了该句柄下的消息回调函数而已。
之所以给具体控件发送点击事件有效,因为在A程序的代码中,有对鼠标点击事件的逻辑处理。发送该消息,A程序就进入了该控件的消息处理回调函数中。
我们的应用程序,如winform,wpf都是基于消息循环机制,相当于我们模拟的动作,是给消息循环插入消息,程序通过循环获取相关的消息后,执行相应操作并得到对应的结果。
所以该操作,必须要指定窗口句柄,系统才知道需要将消息发送至哪里。
具体代码如下:
/// <summary>
///
/// </summary>
/// <param name="hWnd">handle to destination window</param>
/// <param name="Msg">message</param>
/// <param name="wParam">first message parameter</param>
/// <param name="lParam">second message parameter</param>
/// <returns></returns>
[DllImport("user32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
public static int LogicMouseDown(IntPtr hWnd)
{
return SendMessage(hWnd, WMessages.WM_LBUTTONDOWN, IntPtr.Zero, IntPtr.Zero);
}
public enum WMessages
{
//创建一个窗口
WM_CREATE = 0x01,
//当一个窗口被破坏时发送
WM_DESTROY = 0x02,
//移动一个窗口
WM_MOVE = 0x03,
//改变一个窗口的大小
WM_SIZE = 0x05,
//一个窗口被激活或失去激活状态
WM_ACTIVATE = 0x06,
//一个窗口获得焦点
WM_SETFOCUS = 0x07,
//一个窗口失去焦点
WM_KILLFOCUS = 0x08,
//一个窗口改变成Enable状态
WM_ENABLE = 0x0A,
//设置窗口是否能重画
WM_SETREDRAW = 0x0B,
//应用程序发送此消息来设置一个窗口的文本
WM_SETTEXT = 0x0C,
//应用程序发送此消息来复制对应窗口的文本到缓冲区
WM_GETTEXT = 0x0D,
//得到与一个窗口有关的文本的长度(不包含空字符)
WM_GETTEXTLENGTH = 0x0E,
//要求一个窗口重画自己
WM_PAINT = 0x0F,
//当一个窗口或应用程序要关闭时发送一个信号
WM_CLOSE = 0x10,
//当用户选择结束对话框或程序自己调用ExitWindows函数
WM_QUERYENDSESSION = 0x11,
//用来结束程序运行
WM_QUIT = 0x12,
//当用户窗口恢复以前的大小位置时,把此消息发送给某个图标
WM_QUERYOPEN = 0x13,
//当窗口背景必须被擦除时(例在窗口改变大小时)
WM_ERASEBKGND = 0x14,
//当系统颜色改变时,发送此消息给所有顶级窗口
WM_SYSCOLORCHANGE = 0x15,
//当系统进程发出WM_QUERYENDSESSION消息后,此消息发送给应用程序,通知它对话是否结束
WM_ENDSESSION = 0x16,
//当隐藏或显示窗口是发送此消息给这个窗口
WM_SHOWWINDOW = 0x18,
//发此消息给应用程序哪个窗口是激活的,哪个是非激活的
WM_ACTIVATEAPP = 0x1C,
//当系统的字体资源库变化时发送此消息给所有顶级窗口
WM_FONTCHANGE = 0x1D,
//当系统的时间变化时发送此消息给所有顶级窗口
WM_TIMECHANGE = 0x1E,
//发送此消息来取消某种正在进行的摸态(操作)
WM_CANCELMODE = 0x1F,
//如果鼠标引起光标在某个窗口中移动且鼠标输入没有被捕获时,就发消息给某个窗口
WM_SETCURSOR = 0x20,
//当光标在某个非激活的窗口中而用户正按着鼠标的某个键发送此消息给//当前窗口
WM_MOUSEACTIVATE = 0x21,
//发送此消息给MDI子窗口//当用户点击此窗口的标题栏,或//当窗口被激活,移动,改变大小
WM_CHILDACTIVATE = 0x22,
//此消息由基于计算机的训练程序发送,通过WH_JOURNALPALYBACK的hook程序分离出用户输入消息
WM_QUEUESYNC = 0x23,
//此消息发送给窗口当它将要改变大小或位置
WM_GETMINMAXINFO = 0x24,
//发送给最小化窗口当它图标将要被重画
WM_PAINTICON = 0x26,
//此消息发送给某个最小化窗口,仅//当它在画图标前它的背景必须被重画
WM_ICONERASEBKGND = 0x27,
//发送此消息给一个对话框程序去更改焦点位置
WM_NEXTDLGCTL = 0x28,
//每当打印管理列队增加或减少一条作业时发出此消息
WM_SPOOLERSTATUS = 0x2A,
//当button,combobox,listbox,menu的可视外观改变时发送
WM_DRAWITEM = 0x2B,
//当button, combo box, list box, list view control, or menu item 被创建时
WM_MEASUREITEM = 0x2C,
//此消息有一个LBS_WANTKEYBOARDINPUT风格的发出给它的所有者来响应WM_KEYDOWN消息
WM_VKEYTOITEM = 0x2E,
//此消息由一个LBS_WANTKEYBOARDINPUT风格的列表框发送给他的所有者来响应WM_CHAR消息
WM_CHARTOITEM = 0x2F,
//当绘制文本时程序发送此消息得到控件要用的颜色
WM_SETFONT = 0x30,
//应用程序发送此消息得到当前控件绘制文本的字体
WM_GETFONT = 0x31,
//应用程序发送此消息让一个窗口与一个热键相关连
WM_SETHOTKEY = 0x32,
//应用程序发送此消息来判断热键与某个窗口是否有关联
WM_GETHOTKEY = 0x33,
//此消息发送给最小化窗口,当此窗口将要被拖放而它的类中没有定义图标,应用程序能返回一个图标或光标的句柄,当用户拖放图标时系统显示这个图标或光标
WM_QUERYDRAGICON = 0x37,
//发送此消息来判定combobox或listbox新增加的项的相对位置
WM_COMPAREITEM = 0x39,
//显示内存已经很少了
WM_COMPACTING = 0x41,
//发送此消息给那个窗口的大小和位置将要被改变时,来调用setwindowpos函数或其它窗口管理函数
WM_WINDOWPOSCHANGING = 0x46,
//发送此消息给那个窗口的大小和位置已经被改变时,来调用setwindowpos函数或其它窗口管理函数
WM_WINDOWPOSCHANGED = 0x47,
//当系统将要进入暂停状态时发送此消息
WM_POWER = 0x48,
//当一个应用程序传递数据给另一个应用程序时发送此消息
WM_COPYDATA = 0x4A,
//当某个用户取消程序日志激活状态,提交此消息给程序
WM_CANCELJOURNA = 0x4B,
//当某个控件的某个事件已经发生或这个控件需要得到一些信息时,发送此消息给它的父窗口
WM_NOTIFY = 0x4E,
//当用户选择某种输入语言,或输入语言的热键改变
WM_INPUTLANGCHANGEREQUEST = 0x50,
//当平台现场已经被改变后发送此消息给受影响的最顶级窗口
WM_INPUTLANGCHANGE = 0x51,
//当程序已经初始化windows帮助例程时发送此消息给应用程序
WM_TCARD = 0x52,
//此消息显示用户按下了F1,如果某个菜单是激活的,就发送此消息个此窗口关联的菜单,否则就发送给有焦点的窗口,如果//当前都没有焦点,就把此消息发送给//当前激活的窗口
WM_HELP = 0x53,
//当用户已经登入或退出后发送此消息给所有的窗口,//当用户登入或退出时系统更新用户的具体设置信息,在用户更新设置时系统马上发送此消息
WM_USERCHANGED = 0x54,
//公用控件,自定义控件和他们的父窗口通过此消息来判断控件是使用ANSI还是UNICODE结构
WM_NOTIFYFORMAT = 0x55,
//当用户某个窗口中点击了一下右键就发送此消息给这个窗口
// WM_CONTEXTMENU = ??,
//当调用SETWINDOWLONG函数将要改变一个或多个 窗口的风格时发送此消息给那个窗口
WM_STYLECHANGING = 0x7C,
//当调用SETWINDOWLONG函数一个或多个 窗口的风格后发送此消息给那个窗口
WM_STYLECHANGED = 0x7D,
//当显示器的分辨率改变后发送此消息给所有的窗口
WM_DISPLAYCHANGE = 0x7E,
//此消息发送给某个窗口来返回与某个窗口有关连的大图标或小图标的句柄
WM_GETICON = 0x7F,
//程序发送此消息让一个新的大图标或小图标与某个窗口关联
WM_SETICON = 0x80,
//当某个窗口第一次被创建时,此消息在WM_CREATE消息发送前发送
WM_NCCREATE = 0x81,
//此消息通知某个窗口,非客户区正在销毁
WM_NCDESTROY = 0x82,
//当某个窗口的客户区域必须被核算时发送此消息
WM_NCCALCSIZE = 0x83,
//移动鼠标,按住或释放鼠标时发生
WM_NCHITTEST = 0x84,
//程序发送此消息给某个窗口当它(窗口)的框架必须被绘制时
WM_NCPAINT = 0x85,
//此消息发送给某个窗口仅当它的非客户区需要被改变来显示是激活还是非激活状态
WM_NCACTIVATE = 0x86,
//发送此消息给某个与对话框程序关联的控件,widdows控制方位键和TAB键使输入进入此控件通过应
WM_GETDLGCODE = 0x87,
//当光标在一个窗口的非客户区内移动时发送此消息给这个窗口 非客户区为:窗体的标题栏及窗 的边框体
WM_NCMOUSEMOVE = 0xA0,
//当光标在一个窗口的非客户区同时按下鼠标左键时提交此消息
WM_NCLBUTTONDOWN = 0xA1,
//当用户释放鼠标左键同时光标某个窗口在非客户区十发送此消息
WM_NCLBUTTONUP = 0xA2,
//当用户双击鼠标左键同时光标某个窗口在非客户区十发送此消息
WM_NCLBUTTONDBLCLK = 0xA3,
//当用户按下鼠标右键同时光标又在窗口的非客户区时发送此消息
WM_NCRBUTTONDOWN = 0xA4,
//当用户释放鼠标右键同时光标又在窗口的非客户区时发送此消息
WM_NCRBUTTONUP = 0xA5,
//当用户双击鼠标右键同时光标某个窗口在非客户区十发送此消息
WM_NCRBUTTONDBLCLK = 0xA6,
//当用户按下鼠标中键同时光标又在窗口的非客户区时发送此消息
WM_NCMBUTTONDOWN = 0xA7,
//当用户释放鼠标中键同时光标又在窗口的非客户区时发送此消息
WM_NCMBUTTONUP = 0xA8,
//当用户双击鼠标中键同时光标又在窗口的非客户区时发送此消息
WM_NCMBUTTONDBLCLK = 0xA9,
//WM_KEYDOWN 按下一个键
WM_KEYDOWN = 0x0100,
//释放一个键
WM_KEYUP = 0x0101,
//按下某键,并已发出WM_KEYDOWN, WM_KEYUP消息
WM_CHAR = 0x102,
//当用translatemessage函数翻译WM_KEYUP消息时发送此消息给拥有焦点的窗口
WM_DEADCHAR = 0x103,
//当用户按住ALT键同时按下其它键时提交此消息给拥有焦点的窗口
WM_SYSKEYDOWN = 0x104,
//当用户释放一个键同时ALT 键还按着时提交此消息给拥有焦点的窗口
WM_SYSKEYUP = 0x105,
//当WM_SYSKEYDOWN消息被TRANSLATEMESSAGE函数翻译后提交此消息给拥有焦点的窗口
WM_SYSCHAR = 0x106,
//当WM_SYSKEYDOWN消息被TRANSLATEMESSAGE函数翻译后发送此消息给拥有焦点的窗口
WM_SYSDEADCHAR = 0x107,
//在一个对话框程序被显示前发送此消息给它,通常用此消息初始化控件和执行其它任务
WM_INITDIALOG = 0x110,
//当用户选择一条菜单命令项或当某个控件发送一条消息给它的父窗口,一个快捷键被翻译
WM_COMMAND = 0x111,
//当用户选择窗口菜单的一条命令或//当用户选择最大化或最小化时那个窗口会收到此消息
WM_SYSCOMMAND = 0x112,
//发生了定时器事件
WM_TIMER = 0x113,
//当一个窗口标准水平滚动条产生一个滚动事件时发送此消息给那个窗口,也发送给拥有它的控件
WM_HSCROLL = 0x114,
//当一个窗口标准垂直滚动条产生一个滚动事件时发送此消息给那个窗口也,发送给拥有它的控件
WM_VSCROLL = 0x115,
//当一个菜单将要被激活时发送此消息,它发生在用户菜单条中的某项或按下某个菜单键,它允许程序在显示前更改菜单
WM_INITMENU = 0x116,
//当一个下拉菜单或子菜单将要被激活时发送此消息,它允许程序在它显示前更改菜单,而不要改变全部
WM_INITMENUPOPUP = 0x117,
//当用户选择一条菜单项时发送此消息给菜单的所有者(一般是窗口)
WM_MENUSELECT = 0x11F,
//当菜单已被激活用户按下了某个键(不同于加速键),发送此消息给菜单的所有者
WM_MENUCHAR = 0x120,
//当一个模态对话框或菜单进入空载状态时发送此消息给它的所有者,一个模态对话框或菜单进入空载状态就是在处理完一条或几条先前的消息后没有消息它的列队中等待
WM_ENTERIDLE = 0x121,
//在windows绘制消息框前发送此消息给消息框的所有者窗口,通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置消息框的文本和背景颜色
WM_CTLCOLORMSGBOX = 0x132,
//当一个编辑型控件将要被绘制时发送此消息给它的父窗口通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置编辑框的文本和背景颜色
WM_CTLCOLOREDIT = 0x133,
//当一个列表框控件将要被绘制前发送此消息给它的父窗口通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置列表框的文本和背景颜色
WM_CTLCOLORLISTBOX = 0x134,
//当一个按钮控件将要被绘制时发送此消息给它的父窗口通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置按纽的文本和背景颜色
WM_CTLCOLORBTN = 0x135,
//当一个对话框控件将要被绘制前发送此消息给它的父窗口通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置对话框的文本背景颜色
WM_CTLCOLORDLG = 0x136,
//当一个滚动条控件将要被绘制时发送此消息给它的父窗口通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置滚动条的背景颜色
WM_CTLCOLORSCROLLBAR = 0x137,
//当一个静态控件将要被绘制时发送此消息给它的父窗口通过响应这条消息,所有者窗口可以 通过使用给定的相关显示设备的句柄来设置静态控件的文本和背景颜色
WM_CTLCOLORSTATIC = 0x138,
//当鼠标轮子转动时发送此消息个当前有焦点的控件
WM_MOUSEWHEEL = 0x20A,
//双击鼠标中键
WM_MBUTTONDBLCLK = 0x209,
//释放鼠标中键
WM_MBUTTONUP = 0x208,
//移动鼠标时发生,同WM_MOUSEFIRST
WM_MOUSEMOVE = 0x200,
//按下鼠标左键
WM_LBUTTONDOWN = 0x201,
//释放鼠标左键
WM_LBUTTONUP = 0x202,
//双击鼠标左键
WM_LBUTTONDBLCLK = 0x203,
//按下鼠标右键
WM_RBUTTONDOWN = 0x204,
//释放鼠标右键
WM_RBUTTONUP = 0x205,
//双击鼠标右键
WM_RBUTTONDBLCLK = 0x206,
//按下鼠标中键
WM_MBUTTONDOWN = 0x207,
WM_USER = 0x0400,
MK_LBUTTON = 0x0001,
MK_RBUTTON = 0x0002,
MK_SHIFT = 0x0004,
MK_CONTROL = 0x0008,
MK_MBUTTON = 0x0010,
MK_XBUTTON1 = 0x0020,
MK_XBUTTON2 = 0x0040,
//…………
}
参数说明:
hWnd:目标窗体或者控件的句柄 msg: 具体要发送的消息 wParam:附加的第一个消息参数,如传递的坐标信息,具体参数请查阅微软官方资料 lParam:附加的第二个消息参数,具体参数请查阅微软官方资料
总结
模拟键盘和鼠标的一些消息或操作,在某些业务场景里面确实会存在。通过不同的业务场景,需要选择不同的方式进行处理解决。
如果我们需要对某个程序控制,我们选择sendmessage
方法。如果我们是要针对整个系统级别的模拟控制,那我们可以选择mouse_event
方法。
sendmessage
方法依赖于具体的窗体句柄参数,以及逻辑焦点(有些控件没有具体的句柄,如WPF控件,这个时候需要依赖于逻辑焦点)。mouse_event
则需要依赖具体的窗体焦点。
需要注意的是:以上方法并不适用于UWP应用程序,UWP存在较高的安全控制策略,不允许直接模拟实现。下文我们会告诉大家,如何在UWP应用程序中模拟鼠标操作。
本文会经常更新,请阅读原文: https://huchengv5.gitee.io//post/C-%E8%99%9A%E6%8B%9F%E9%BC%A0%E6%A0%87%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%B3%95.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名胡承(包含链接: https://huchengv5.gitee.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。