经常会被问到需要点击软件的,主要都是玩游戏的盆友,但是也有其它用途的。所以简单弄了一个,打算每当有时间,有需求,就加一些小功能。
这里主要是要记录一下相关开发工作,也记录一些使用/更新的信息。
【2022/08/22】版本v1.0(初始版本,主要包含间隔点击模式、识别图片点击模式)
目前实现两种点击模式(仅支持鼠标左键单击)。
一句话描述,按设置好的间隔时间鼠标左键单击用户设定的位置。

一句话描述,用户截图想要识别的图片,上传到软件,然后软件按设置好的间隔时间识别屏幕是否出现了用户上传的图片,如果识别到了,则鼠标左键单击用户设定的位置。

后面会增加一些点击模式,比如文字识别,颜色识别等。
主要设置

- internal enum ClickModes
- {
- /// <summary>
- /// 间隔模式
- /// </summary>
- ClickByStep,
- /// <summary>
- /// 根据图片点击
- /// </summary>
- ClickByPictures,
- /// <summary>
- /// 根据文字点击
- /// </summary>
- ClickByText,
- /// <summary>
- /// 根据颜色点击
- /// </summary>
- ClickByColor,
- /// <summary>
- /// 自定义的点击
- /// </summary>
- ClickByCustomize
- }
- public enum SettingModes
- {
- NotSettingState,
-
- /// <summary>
- /// 间隔点击模式的点击位置设置
- /// </summary>
- ClickByStep_PositionOne,
-
- /// <summary>
- /// 根据图片的点击位置设置
- /// </summary>
- ClickByPictures_PositionOne,
-
- /// <summary>
- /// 根据文字的点击位置设置
- /// </summary>
- ClickByText_PositionOne,
-
- /// <summary>
- /// 根据颜色的点击位置设置
- /// </summary>
- ClickByColor_PositionOne,
-
- /// <summary>
- /// 根据自定义的点击设置
- /// </summary>
- ClickByCustomize_PositionOne,
- ClickByCustomize_PositionTwo,
- ClickByCustomize_PositionThree,
- ClickByCustomize_PositionFour,
- ClickByCustomize_PositionFive,
- ClickByCustomize_PositionSix,
- }
- internal class Win32Api
- {
- [StructLayout(LayoutKind.Sequential)]
- public class POINT
- {
- public int x;
- public int y;
- }
- [StructLayout(LayoutKind.Sequential)]
- public class MouseHookStruct
- {
- public POINT pt;
- public int hwnd;
- public int wHitTestCode;
- public int dwExtraInfo;
- }
- public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
- //安装钩子
- [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
- public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
- //卸载钩子
- [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
- public static extern bool UnhookWindowsHookEx(int idHook);
- //调用下一个钩子
- [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
- public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
-
- [DllImport("User32")]
- public extern static void SetCursorPos(int x, int y);
-
- //定义快捷键
- [DllImport("user32.dll", SetLastError = true)]
- public static extern bool RegisterHotKey(IntPtr hWnd, int id, KeyModifiers fsModifiers, int vk);
-
- [Flags()]
- public enum KeyModifiers
- {
- None = 0,
- Alt = 1,
- Ctrl = 2,
- Shift = 4,
- WindowsKey = 8
- }
-
- /// <summary>
- /// 取消注册热键
- /// </summary>
- /// <param name="hWnd">要取消热键的窗口的句柄</param>
- /// <param name="id">要取消热键的ID</param>
- /// <returns></returns>
- [DllImport("user32.dll", SetLastError = true)]
- public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
-
- /// <summary>
- /// 向全局原子表添加一个字符串,并返回这个字符串的唯一标识符,成功则返回值为新创建的原子ID,失败返回0
- /// </summary>
- /// <param name="lpString"></param>
- /// <returns></returns>
- [DllImport("kernel32", SetLastError = true)]
- public static extern short GlobalAddAtom(string lpString);
- /// <summary>
- /// 热键的对应的消息ID
- /// </summary>
- public const int WM_HOTKEY = 0x312;
-
-
- [DllImport("kernel32", SetLastError = true)]
- public static extern short GlobalDeleteAtom(short nAtom);
-
- [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
- public static extern int mouse_event(int dwflags, int dx, int dy, int cbuttons, int dwextrainfo);
-
- 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; //标示是否采用绝对坐标
-
- }
- timer = new System.Timers.Timer();
- timer.Enabled = true;
- timer.Interval = settingModel.intervals; //执行间隔时间,单位为毫秒
- timer.Elapsed += new System.Timers.ElapsedEventHandler(myTimedTask);
- int f1 = Win32Api.GlobalAddAtom("F1");
- int f5 = Win32Api.GlobalAddAtom("F5");
- int f6 = Win32Api.GlobalAddAtom("F6");
- Win32Api.RegisterHotKey(this.Handle, f1, Win32Api.KeyModifiers.None, (int)Keys.F1);
- Win32Api.RegisterHotKey(this.Handle, f5, Win32Api.KeyModifiers.None, (int)Keys.F5);
- Win32Api.RegisterHotKey(this.Handle, f6, Win32Api.KeyModifiers.None, (int)Keys.F6);
- /// <summary>
- /// 监视Windows消息
- /// </summary>
- /// <param name="m"></param>
- protected override void WndProc(ref Message m)
- {
- switch (m.Msg)
- {
- case Win32Api.WM_HOTKEY:
- ProcessHotkey(m);//按下热键时调用ProcessHotkey()函数
- break;
- }
- base.WndProc(ref m); //将系统消息传递自父类的WndProc
- }
-
- /// <summary>
- /// 按下设定的键时调用该函数
- /// </summary>
- /// <param name="m"></param>
- private void ProcessHotkey(Message m)
- {
- IntPtr id = m.WParam;//IntPtr用于表示指针或句柄的平台特定类型
- int sid = id.ToInt32();
- if (sid == f1)
- {
- //do something
- }
- else if (sid == f5)
- {
- //do something
- }
- else if (sid == f6)
- {
- //do something
- }
- }
- /// <summary>
- /// 单击
- /// </summary>
- /// <param name="x"></param>
- /// <param name="y"></param>
- public static void SingleClick(int x, int y)
- {
- //鼠标移动
- Win32Api.SetCursorPos(x, y);
-
- //鼠标点击
- Win32Api.mouse_event(Win32Api.mouseeventf_leftdown, x, y, 0, 0);
- Win32Api.mouse_event(Win32Api.mouseeventf_leftup, x, y, 0, 0);
- }
监控开始的时候,首先检查是否正确设置了点击位置,然后开始定时器。
- switch (clickModes)
- {
- //间隔模式
- case ClickModes.ClickByStep:
- int x1, y1;
- if (!this.control0.ClickPositionFlag(out x1, out y1))
- {
- MessageBox.Show("请检查鼠标点击位置设置是否正确。");
- return;
- }
- break;
- //图片模式
- case ClickModes.ClickByPictures:
- if(this.control1.mat == null)
- {
- MessageBox.Show("请选择待匹配图片。");
- return;
- }
- break;
- }
-
- this.BackColor = Color.AliceBlue;
- this.startFlag = true;
- this.timer.Start();
- public void myTimedTask(object sender, System.Timers.ElapsedEventArgs e)
- {
- if (startFlag)
- {
- switch (this.clickModes)
- {
- //间隔模式
- case ClickModes.ClickByStep:
- int x0, y0;
- if (this.control0.ClickPositionFlag(out x0, out y0))
- {
- HelpTools.SingleClick(x0, y0);
- }
- break;
- //图片模式
- case ClickModes.ClickByPictures:
- int x2, y2;
- //检查是否再屏幕内匹配到用户上传的图片
- if(this.GetShouldClickFlagByPictures(out x2, out y2))
- {
- int x1, y1;
- //如果用户设置了点击位置,则点击设置的位置
- if(this.control1.ClickPositionFlag(out x1, out y1))
- {
- HelpTools.SingleClick(x1, y1);
- }
- //如果用户没有设置点击位置,则点击图片中心
- else
- {
- HelpTools.SingleClick(x2, y2);
- }
- }
- break;
- case ClickModes.ClickByText:
-
- break;
- case ClickModes.ClickByColor:
-
- break;
- case ClickModes.ClickByCustomize:
-
- break;
-
- default:
- break;
- }
-
- // 用于统计点击的次数
- runtimes++;
- // 如果是点击固定次数,则点击次数达到了的时候,停止运行
- if(settingModel!=null && settingModel.fixedFlag && runtimes>= settingModel.clickNums)
- {
- this.Invoke(new Action(() =>this.button5_Click(null, null)));
- }
- }
- }
首先全屏截图,然后从全屏截图中使用用户上传的图片进行模板匹配,找到匹配的区域之后再将该区域抠出来和用户上传的图片进行一次结构相似性的比较(故此说这里未必次次都能正确找到,但是一般辨识度高的图片问题不太大)。
- /// <summary>
- /// 判断图片模式是否需要点击
- /// </summary>
- /// <returns></returns>
- public bool GetShouldClickFlagByPictures(out int x1, out int y1)
- {
- //全屏截图
- int width = Screen.PrimaryScreen.Bounds.Width;
- int height = Screen.PrimaryScreen.Bounds.Height;
- Rectangle smallRect = new Rectangle(0, 0, width, height);
- Rectangle tScreenRect = new Rectangle(0, 0, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
- Bitmap tSrcBmp = new Bitmap(width, height); // 用于屏幕原始图片保存
- Graphics gp = Graphics.FromImage(tSrcBmp);
- gp.CopyFromScreen(0, 0, 0, 0, smallRect.Size);
- gp.DrawImage(tSrcBmp, 0, 0, smallRect, GraphicsUnit.Pixel);
-
- //全屏截图转mat
- Mat mat = OpenCvSharp.Extensions.BitmapConverter.ToMat(tSrcBmp);
- Cv2.CvtColor(mat, mat, ColorConversionCodes.BGRA2BGR);
-
- //从全屏截图查找待匹配图像
- Mat out1 = new Mat();
- Cv2.MatchTemplate(mat, this.control1.mat, out1, TemplateMatchModes.CCoeffNormed);
-
- //寻找匹配到的最大最小值
- double minVal, maxVal;
- OpenCvSharp.Point minLoc, maxLoc;
- Cv2.MinMaxLoc(out1, out minVal, out maxVal, out minLoc, out maxLoc, null);
- //Cv2.Rectangle(mat, maxLoc, new OpenCvSharp.Point(maxLoc.X + this.control1.mat.Width, maxLoc.Y + this.control1.mat.Height), 255, 2);
-
- //获取匹配到的部分图像
- Mat child = new Mat(mat, new OpenCvSharp.Rect(maxLoc.X, maxLoc.Y, this.control1.mat.Width, this.control1.mat.Height));
-
- x1 = maxLoc.X + this.control1.mat.Width / 2;
- y1 = maxLoc.Y + this.control1.mat.Height / 2;
-
- //比较匹配到的和原图的相似度
- double ssim = ImagesTools.Compare_SSIM(child.Clone(), this.control1.mat.Clone());
- Console.WriteLine("相似性:" + ssim);
- if (ssim > 0.9)
- {
- return true;
- }
-
- System.GC.Collect();
- return false;
- }