今天接到一个需求,要求开发一个定制的小工具,运行后,能自动化测试一个桌面应用,不能用按键精灵,于是,我想到了可以利用C#开发一个调用WindowsAPI的接口,来实现该需求,以下是具体步骤
首先我们先申明一个鼠标的事件类,用来标识鼠标的各个事件
public static class Mouse
{
//移动鼠标
public const int MOUSEEVENTF_MOVE = 0x0001;
//模拟鼠标左键按下
public const int MOUSEEVENTF_LEFTDOWN = 0x0002;
//模拟鼠标左键抬起
public const int MOUSEEVENTF_LEFTUP = 0x0004;
//模拟鼠标右键按下
public const int MOUSEEVENTF_RIGHTDOWN = 0x0008;
//模拟鼠标右键抬起
public const int MOUSEEVENTF_RIGHTUP = 0x0010;
//模拟鼠标中键按下
public const int MOUSEEVENTF_MIDDLEDOWN = 0x0020;
//模拟鼠标中键抬起
public const int MOUSEEVENTF_MIDDLEUP = 0x0040;
//标示是否采用绝对坐标
public const int MOUSEEVENTF_ABSOLUTE = 0x8000;
}
其次,我们再申明一个键盘类,用来映射键盘的各个物理按键
public static class Keyboard
{
#region bVk参数 常量定义
public const int vbKeyLButton = 0x1; // 鼠标左键
public const int vbKeyRButton = 0x2; // 鼠标右键
public const int vbKeyCancel = 0x3; // CANCEL 键
public const int vbKeyMButton = 0x4; // 鼠标中键
public const int vbKeyBack = 0x8; // BACKSPACE 键
public const int vbKeyTab = 0x9; // TAB 键
public const int vbKeyClear = 0xC; // CLEAR 键
public const int vbKeyReturn = 0xD; // ENTER 键
public const int vbKeyShift = 0x10; // SHIFT 键
public const int vbKeyControl = 0x11; // CTRL 键
public const int vbKeyAlt = 18; // Alt 键 (键码18)
public const int vbKeyMenu = 0x12; // MENU 键
public const int vbKeyPause = 0x13; // PAUSE 键
public const int vbKeyCapital = 0x14; // CAPS LOCK 键
public const int vbKeyEscape = 0x1B; // ESC 键
public const int vbKeySpace = 0x20; // SPACEBAR 键
public const int vbKeyPageUp = 0x21; // PAGE UP 键
public const int vbKeyEnd = 0x23; // End 键
public const int vbKeyHome = 0x24; // HOME 键
public const int vbKeyLeft = 0x25; // LEFT ARROW 键
public const int vbKeyUp = 0x26; // UP ARROW 键
public const int vbKeyRight = 0x27; // RIGHT ARROW 键
public const int vbKeyDown = 0x28; // DOWN ARROW 键
public const int vbKeySelect = 0x29; // Select 键
public const int vbKeyPrint = 0x2A; // PRINT SCREEN 键
public const int vbKeyExecute = 0x2B; // EXECUTE 键
public const int vbKeySnapshot = 0x2C; // SNAPSHOT 键
public const int vbKeyDelete = 0x2E; // Delete 键
public const int vbKeyHelp = 0x2F; // HELP 键
public const int vbKeyNumlock = 0x90; // NUM LOCK 键
//常用键 字母键A到Z
public const int vbKeyA = 65;
public const int vbKeyB = 66;
public const int vbKeyC = 67;
public const int vbKeyD = 68;
public const int vbKeyE = 69;
public const int vbKeyF = 70;
public const int vbKeyG = 71;
public const int vbKeyH = 72;
public const int vbKeyI = 73;
public const int vbKeyJ = 74;
public const int vbKeyK = 75;
public const int vbKeyL = 76;
public const int vbKeyM = 77;
public const int vbKeyN = 78;
public const int vbKeyO = 79;
public const int vbKeyP = 80;
public const int vbKeyQ = 81;
public const int vbKeyR = 82;
public const int vbKeyS = 83;
public const int vbKeyT = 84;
public const int vbKeyU = 85;
public const int vbKeyV = 86;
public const int vbKeyW = 87;
public const int vbKeyX = 88;
public const int vbKeyY = 89;
public const int vbKeyZ = 90;
//数字键盘0到9
public const int vbKey0 = 48; // 0 键
public const int vbKey1 = 49; // 1 键
public const int vbKey2 = 50; // 2 键
public const int vbKey3 = 51; // 3 键
public const int vbKey4 = 52; // 4 键
public const int vbKey5 = 53; // 5 键
public const int vbKey6 = 54; // 6 键
public const int vbKey7 = 55; // 7 键
public const int vbKey8 = 56; // 8 键
public const int vbKey9 = 57; // 9 键
public const int vbKeyNumpad0 = 0x60; //0 键
public const int vbKeyNumpad1 = 0x61; //1 键
public const int vbKeyNumpad2 = 0x62; //2 键
public const int vbKeyNumpad3 = 0x63; //3 键
public const int vbKeyNumpad4 = 0x64; //4 键
public const int vbKeyNumpad5 = 0x65; //5 键
public const int vbKeyNumpad6 = 0x66; //6 键
public const int vbKeyNumpad7 = 0x67; //7 键
public const int vbKeyNumpad8 = 0x68; //8 键
public const int vbKeyNumpad9 = 0x69; //9 键
public const int vbKeyMultiply = 0x6A; // MULTIPLICATIONSIGN(*)键
public const int vbKeyAdd = 0x6B; // PLUS SIGN(+) 键
public const int vbKeySeparator = 0x6C; // ENTER 键
public const int vbKeySubtract = 0x6D; // MINUS SIGN(-) 键
public const int vbKeyDecimal = 0x6E; // DECIMAL POINT(.) 键
public const int vbKeyDivide = 0x6F; // DIVISION SIGN(/) 键
//F1到F12按键
public const int vbKeyF1 = 0x70; //F1 键
public const int vbKeyF2 = 0x71; //F2 键
public const int vbKeyF3 = 0x72; //F3 键
public const int vbKeyF4 = 0x73; //F4 键
public const int vbKeyF5 = 0x74; //F5 键
public const int vbKeyF6 = 0x75; //F6 键
public const int vbKeyF7 = 0x76; //F7 键
public const int vbKeyF8 = 0x77; //F8 键
public const int vbKeyF9 = 0x78; //F9 键
public const int vbKeyF10 = 0x79; //F10 键
public const int vbKeyF11 = 0x7A; //F11 键
public const int vbKeyF12 = 0x7B; //F12 键
#endregion
}
申明窗体坐标位置类
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; //最左坐标
public int Top; //最上坐标
public int Right; //最右坐标
public int Bottom; //最下坐标
}
然后,申明Action类,用来引入WINDOWSAPI的接口
public static class Action
{
public static int WM_CHAR = 0x102; //键入事件
public static int WM_CLICK = 0x00F5; //点击事件
public static int WM_CLOSE = 0x0010; //窗体关闭事件
public static int CB_SETCURSEL = 0x014E; //下拉框搜索
public static int CB_FINDSTRINGEXACT = 0x0158; //下拉框选择
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, string lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter,
string lpszClass, string lpszWindow);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
public static extern int AnyPopup();
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
public static extern int EnumThreadWindows(IntPtr dwThreadId, CallBack lpfn, int lParam);
[DllImport("user32.dll")]
public static extern int EnumChildWindows(IntPtr hWndParent, CallBack lpfn, int lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr PostMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr SendMessageA(IntPtr hwnd, int wMsg, int wParam, int lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
public static extern IntPtr GetParent(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern int mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);
[DllImport("user32.dll")]
public static extern bool SetCursorPos(int X, int Y);
[DllImport("user32.dll")]
public static extern void keybd_event(int bVk, int bScan, int dwFlags, int dwExtraInfo);
public delegate bool CallBack(IntPtr hwnd, int lParam);
}
接下来,我们就可以封装方法使用这些接口,在此,我封装了一个MessageHelp方法,里面有一些常用的事件,比如可按照如下思想设计“按照窗体名称定位句柄”“定位到句柄后给句柄设置文字”;“把鼠标移动指定坐标位置并点击”“找到指定文字的按钮并点击”
public static class MessageHelp
{
/// <summary>
/// windows像句柄设置文字
/// </summary>
/// <param name="hand"></param>
/// <param name="ch"></param>
/// <param name="SleepTime"></param>
public static void SendChar(IntPtr hand, char ch, int SleepTime = 10)
{
BaseHelp.Action.PostMessage(hand, BaseHelp.Action.WM_CHAR, ch, 0);
Thread.Sleep(SleepTime);
}
/// <summary>
/// windows关闭句柄窗体
/// </summary>
/// <param name="hwnd"></param>
/// <param name="SleepTime"></param>
public static void CloseWindow(IntPtr hwnd, int SleepTime = 100)
{
BaseHelp.Action.PostMessage(hwnd, BaseHelp.Action.WM_CLOSE, 0, 0);
Thread.Sleep(SleepTime);
}
/// <summary>
/// 移动鼠标位置(相对于句柄窗体位置)
/// </summary>
/// <param name="X"></param>
/// <param name="Y"></param>
/// <param name="WindowRect"></param>
/// <param name="SleepTime"></param>
public static void SetCursorPos(int X,int Y, BaseHelp.RECT WindowRect, int SleepTime = 10)
{
BaseHelp.Action.SetCursorPos(X + (WindowRect.Left), Y + (WindowRect.Top));
Thread.Sleep(SleepTime);
}
/// <summary>
/// 鼠标左键单击
/// </summary>
/// <param name="SleepTime"></param>
public static void MouseLEFTDown(int SleepTime = 10)
{
//模拟鼠标按下操作
BaseHelp.Action.mouse_event((int)(BaseHelp.Mouse.MOUSEEVENTF_LEFTDOWN | BaseHelp.Mouse.MOUSEEVENTF_LEFTDOWN), 0, 0, 0, 0);
Thread.Sleep(SleepTime);
}
/// <summary>
/// 查找窗体里句柄
/// </summary>
/// <param name="lpClassName"></param>
/// <param name="lpWindowName"></param>
/// <returns></returns>
public static IntPtr FindWindow(string lpClassName, string lpWindowName)
{
return BaseHelp.Action.FindWindow(lpClassName, lpWindowName);
}
/// <summary>
/// 查找窗体里的控件句柄
/// </summary>
/// <param name="hwndParent"></param>
/// <param name="hwndChildAfter"></param>
/// <param name="lpszClass"></param>
/// <param name="lpszWindow"></param>
/// <returns></returns>
public static IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter,string lpszClass, string lpszWindow)
{
return BaseHelp.Action.FindWindowEx(hwndParent, hwndChildAfter, lpszClass, lpszWindow);
}
/// <summary>
/// 遍历获取Windows的控件子句柄
/// </summary>
/// <param name="hWndParent"></param>
/// <param name="lpfn"></param>
/// <param name="lParam"></param>
/// <returns></returns>
public static int EnumChildWindows(IntPtr hWndParent, CallBack lpfn, int lParam)
{
return BaseHelp.Action.EnumChildWindows(hWndParent, lpfn, lParam);
}
/// <summary>
/// 获取Windows窗体坐标
/// </summary>
/// <param name="hWnd"></param>
/// <param name="lpRect"></param>
/// <returns></returns>
public static bool GetWindowRect(IntPtr hWnd, ref RECT lpRect)
{
return BaseHelp.Action.GetWindowRect(hWnd, ref lpRect);
}
/// <summary>
/// 发送异步Windows消息
/// </summary>
/// <param name="hwnd"></param>
/// <param name="wMsg"></param>
/// <param name="wParam"></param>
/// <param name="lParam"></param>
/// <param name="SleepTime"></param>
/// <returns></returns>
public static IntPtr PostMessage(IntPtr hwnd, int wMsg, int wParam, int lParam)
{
return BaseHelp.Action.PostMessage(hwnd, wMsg, wParam, lParam);
}
/// <summary>
/// 发送同步Windows消息
/// </summary>
/// <param name="hWnd"></param>
/// <param name="Msg"></param>
/// <param name="wParam"></param>
/// <param name="lParam"></param>
/// <returns></returns>
public static IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam)
{
return BaseHelp.Action.SendMessage(hWnd, Msg, wParam, lParam);
}
/// <summary>
/// 调用键盘事件
/// </summary>
/// <param name="bVk"></param>
/// <param name="bScan"></param>
/// <param name="dwFlags"></param>
/// <param name="dwExtraInfo"></param>
/// <param name="SleepTime"></param>
public static void keybd_event(int bVk, int bScan, int dwFlags, int dwExtraInfo, int SleepTime = 10)
{
BaseHelp.Action.keybd_event(bVk, bScan, dwFlags, dwExtraInfo);
Thread.Sleep(SleepTime);
}
/// <summary>
/// 切换前台窗体
/// </summary>
/// <param name="hWnd"></param>
/// <returns></returns>
public static bool SetForegroundWindow(IntPtr hWnd)
{
return BaseHelp.Action.SetForegroundWindow(hWnd);
}
/// <summary>
/// 查找定位窗体句柄
/// </summary>
/// <param name="WindowsIntPtr"></param>
/// <param name="Action"></param>
/// <returns></returns>
public static List<MessageHelp.MatchIntPtr> FindControl(IntPtr WindowsIntPtr, MessageHelp.ActionDetail Action)
{
List<MessageHelp.MatchIntPtr> result = new List<MessageHelp.MatchIntPtr>();
if(!string.IsNullOrEmpty(Action.SpyID))
{
return FindControlBySpyID(WindowsIntPtr, Action);
}
else if(Action.Points!=null && Action.Points.Count>0)
{
return FindControlByPoint(WindowsIntPtr, Action);
}
else
{
return FindControlByName(WindowsIntPtr, Action);
}
}
/// <summary>
/// 根据SPYID,查找窗体
/// </summary>
/// <param name="WindowsIntPtr"></param>
/// <param name="SpyID"></param>
/// <returns></returns>
private static List<MessageHelp.MatchIntPtr> FindControlBySpyID(IntPtr WindowsIntPtr, MessageHelp.ActionDetail Action)
{
List<MessageHelp.MatchIntPtr> result = new List<MessageHelp.MatchIntPtr>();
MessageHelp.EnumChildWindows(WindowsIntPtr, new BaseHelp.Action.CallBack(delegate (IntPtr hwnd, int lParam)
{
StringBuilder title = new StringBuilder(256);
GetClassName(hwnd, title, title.Capacity);
if (title.ToString() == Action.SpyID)
{
result.Add(new MessageHelp.MatchIntPtr()
{
intPtr = hwnd,
});
}
return true;
}), 0);
return result;
}
/// <summary>
/// 根据坐标点,查找窗体的WINFORM控件句柄
/// </summary>
/// <param name="WindowsIntPtr"></param>
/// <param name="Action"></param>
/// <returns></returns>
private static List<MessageHelp.MatchIntPtr> FindControlByPoint(IntPtr WindowsIntPtr, MessageHelp.ActionDetail Action)
{
List<MessageHelp.MatchIntPtr> result = new List<MessageHelp.MatchIntPtr>();
RECT WindowsRect = new RECT();
#region 获取窗体的绝对坐标
MessageHelp.GetWindowRect(WindowsIntPtr, ref WindowsRect);
#endregion
MessageHelp.EnumChildWindows(WindowsIntPtr, new BaseHelp.Action.CallBack(delegate (IntPtr hwnd, int lParam)
{
RECT clientRect = new RECT();
#region 获取控件的绝对坐标
MessageHelp.GetWindowRect(hwnd, ref clientRect);
#endregion
#region 计算相对坐标
int Left = clientRect.Left - WindowsRect.Left;
int Right = Left + (clientRect.Right - clientRect.Left);
int Top = clientRect.Top - WindowsRect.Top;
int Bottom = Top + (clientRect.Bottom - clientRect.Top);
#endregion
foreach (var Point in Action.Points)
{
if (Top <= Point.PointY && Bottom >= Point.PointY && Left <= Point.PointX && Right >= Point.PointX)
{
result.Add(new MessageHelp.MatchIntPtr()
{
intPtr = hwnd,
});
}
}
return true;
}), 0);
return result;
}
/// <summary>
/// 根据标题名称,查找窗体的WINFORM控件句柄
/// </summary>
/// <param name="WindowsIntPtr"></param>
/// <param name="Action"></param>
/// <returns></returns>
private static List<MessageHelp.MatchIntPtr> FindControlByName(IntPtr WindowsIntPtr, MessageHelp.ActionDetail Action)
{
List<MessageHelp.MatchIntPtr> result = new List<MessageHelp.MatchIntPtr>();
MessageHelp.EnumChildWindows(WindowsIntPtr, new BaseHelp.Action.CallBack(delegate (IntPtr hwnd, int lParam)
{
StringBuilder title = new StringBuilder();
GetWindowText(hwnd, title, 200);
if (title.ToString() == Action.CharMsg)
{
result.Add(new MessageHelp.MatchIntPtr()
{
intPtr = hwnd,
});
}
return true;
}), 0);
return result;
}
/// <summary>
/// 切图保存
/// </summary>
/// <param name="folderPath"></param>
/// <param name="fileName"></param>
public static void CaptureScreenAndSave(string folderPath, string fileName)
{
// 确保文件夹存在
Directory.CreateDirectory(folderPath);
// 创建完整的文件路径
string filePath = Path.Combine(folderPath, fileName);
// 获取整个屏幕的尺寸
Rectangle bounds = Screen.PrimaryScreen.Bounds;
// 创建一个bitmap
using (Bitmap bmp = new Bitmap(bounds.Width, bounds.Height))
{
// 创建一个画布
using (Graphics g = Graphics.FromImage(bmp))
{
// 截取屏幕
g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
}
// 保存截图到文件
bmp.Save(filePath, ImageFormat.Png);
}
}
public class ActionDetail
{
public string Form { get; set; }
public List<ActionPoint> Points { get; set; }
public string CharMsg { get; set; }
public string SpyID { get; set; }
}
public class ActionPoint
{
public int PointX { get; set; }
public int PointY { get; set; }
}
public class MatchIntPtr
{
public IntPtr intPtr { get; set; }
}
}
其中SpyID的获取方法,可以使用VS自带的Spy++工具来进行选点
注意:以下部分是伪代码,旨意再说明上述方法的使用,其中Actions对象是一系列准备待操作鼠标和键盘事件的集合,将依次执行,Action.Job_Type是定义的枚举事件的方法
foreach (var Action in Actions)
{
IntPtr hwnd_Form = MessageHelp.FindWindow(null, Action.Action.Form);
if (hwnd_Form == IntPtr.Zero && Action.Job_Type != "009" && Action.Job_Type != "007" && Action.Job_Type != "006")
{
continue;
}
if (Action.Job_Type == "001") //文字键入
{
List<MessageHelp.MatchIntPtr> MatchsCos = new List<MessageHelp.MatchIntPtr>();
MatchsCos = MessageHelp.FindControl(hwnd_Form, Action.Action);
foreach (var MatchsCo in MatchsCos)
{
char[] UserChar = Action.Action.CharMsg.ToCharArray();
foreach (char ch in UserChar)
{
MessageHelp.SendChar(MatchsCo.intPtr, ch);
}
}
}
else if (Action.Job_Type == "002") //鼠标移动
{
RECT rect = new RECT();
MessageHelp.GetWindowRect(hwnd_Form, ref rect);
MessageHelp.SetCursorPos(Action.Action.Points[0].PointX, Action.Action.Points[0].PointY, rect);
}
else if (Action.Job_Type == "003")//鼠标左击
{
MessageHelp.MouseLEFTDown();
}
else if (Action.Job_Type == "004")//按钮点击
{
var MatchsCos = MessageHelp.FindControl(hwnd_Form, Action.Action);
foreach (var MatchsCo in MatchsCos)
{
MessageHelp.PostMessage(MatchsCo.intPtr, BaseHelp.Action.WM_CLICK, (int)hwnd_Form, 0);
}
}
else if (Action.Job_Type == "005")//下拉选择
{
var MatchsCos = MessageHelp.FindControl(hwnd_Form, Action.Action);
foreach (var MatchsCo in MatchsCos)
{
int index = (int)MessageHelp.SendMessage(MatchsCo.intPtr, BaseHelp.Action.CB_FINDSTRINGEXACT, IntPtr.Zero, Marshal.StringToHGlobalAuto(Action.Action.CharMsg));
MessageHelp.SendMessage(MatchsCo.intPtr, BaseHelp.Action.CB_SETCURSEL, (IntPtr)index, (IntPtr)0);
}
}
else if (Action.Job_Type == "006")//按下回车
{
MessageHelp.keybd_event(BaseHelp.Keyboard.vbKeyReturn, 0, 0, 0);
}
else if (Action.Job_Type == "007")//按下ESC
{
MessageHelp.keybd_event(BaseHelp.Keyboard.vbKeyEscape, 0, 0, 0);
}
else if(Action.Job_Type== "011")//按下TAB
{
MessageHelp.keybd_event(BaseHelp.Keyboard.vbKeyTab, 0, 0, 0);
}
else if (Action.Job_Type == "008")//关闭窗体
{
MessageHelp.SendMessage(hwnd_Form, BaseHelp.Action.WM_CLOSE, (IntPtr)0, (IntPtr)0);
}
else if (Action.Job_Type == "009")//截图保存
{
var Now = DateTime.Now;
string currentDirectory = Application.StartupPath;
MessageHelp.CaptureScreenAndSave($@"{currentDirectory}/{DataSets.Rows[0]["Job_Name"].ToString()}/{Now.ToString("yyyyMMdd")}", $@"{DataSets.Rows[0]["Job_Title"].ToString()}{Now.ToString("yyyyMMddHHmmssfff")}.png");
}
else if (Action.Job_Type == "010")//等待时间
{
Thread.Sleep(Convert.ToInt32(Action.Action.CharMsg));
}
Thread.Sleep(2000);
}