因在游戏公司工作,游戏运行测试,偶尔需要测试升级或其他操作需要鼠标持续点击,网上下载的连点器广告太多,所以想自己编写了一款鼠标连点工具。
通过命令或操作,使鼠标特定按键 持续、有规律的点击屏幕指定位置。
使用Hook(钩子),监听键盘按下事件。
根据按键获取到按下按键的键值(Keys)。
判断该键值是否为命令热键(快捷键),(是否为 开始 | 结束)。
如果是开始,则判断当前工具的状态是否为已开始,如果是,则不执行,否则执行开启连点器功能。
如果是结束,则判断当前工具的状态是否为已开始,如果是,则执行结束命令,否则不执行。
判断该键值是否为紧急结束功能快捷键(ESC键)
为了防止误触,判定需要连续按键4次以上,且每次按键间隔不超过1.5秒。则为有效执行。否则视为无效操作。
鼠标点击相关:
调用了Windows自带的Dll动态连接库 user32.dll 操作鼠标左键的按下、左键抬起,右键按下、右键抬起、移动鼠标等。
使用Timer控件,实现了定时执行点击命令。
其他相关:
使用 Configuration 保存工具配置信息。
钩子相关:
KeyboardHookLib.cs类
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Diagnostics;
using Microsoft.Win32;
using System.Windows.Forms;
namespace MouseClicker
{
public class KeyboardHookLib
{
private const int WH_KEYBOARD_LL = 13; //键盘
//键盘处理事件委托 ,当捕获键盘输入时调用定义该委托的方法.
private delegate int HookHandle(int nCode, int wParam, IntPtr lParam);
//客户端键盘处理事件
public delegate void ProcessKeyHandle(HookStruct param, out bool handle);
//接收SetWindowsHookEx返回值
private static int _hHookValue = 0;
//勾子程序处理事件
private HookHandle _KeyBoardHookProcedure;
//Hook结构
[StructLayout(LayoutKind.Sequential)]
public class HookStruct
{
public int vkCode;
public int scanCode;
public int flags;
public int time;
public int dwExtraInfo;
}
//设置钩子
[DllImport("user32.dll")]
private static extern int SetWindowsHookEx(int idHook, HookHandle lpfn, IntPtr hInstance, int threadId);
//取消钩子
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern bool UnhookWindowsHookEx(int idHook);
//调用下一个钩子
[DllImport("user32.dll")]
private static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam);
//获取当前线程ID
[DllImport("kernel32.dll")]
private static extern int GetCurrentThreadId();
//Gets the main module for the associated process.
[DllImport("kernel32.dll")]
private static extern IntPtr GetModuleHandle(string name);
private IntPtr _hookWindowPtr = IntPtr.Zero;
//构造器
public KeyboardHookLib() { }
//外部调用的键盘处理事件
private static ProcessKeyHandle _clientMethod = null;
///
/// 安装勾子
///
/// 外部调用的键盘处理事件
public void InstallHook(ProcessKeyHandle clientMethod)
{
_clientMethod = clientMethod;
// 安装键盘钩子
if (_hHookValue == 0)
{
_KeyBoardHookProcedure = new HookHandle(OnHookProc);
_hookWindowPtr = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
_hHookValue = SetWindowsHookEx(
WH_KEYBOARD_LL,
_KeyBoardHookProcedure,
_hookWindowPtr,
0);
//如果设置钩子失败.
if (_hHookValue == 0) UninstallHook();
}
}
//取消钩子事件
public void UninstallHook()
{
if (_hHookValue != 0)
{
bool ret = UnhookWindowsHookEx(_hHookValue);
if (ret) _hHookValue = 0;
}
}
//钩子事件内部调用,调用_clientMethod方法转发到客户端应用。
private static int OnHookProc(int nCode, int wParam, IntPtr lParam)
{
if (nCode >= 0)
{
//转换结构
HookStruct hookStruct = (HookStruct)Marshal.PtrToStructure(lParam, typeof(HookStruct));
// hookStruct.vkCode = 0;
if (_clientMethod != null)
{
bool handle = false;
//调用客户提供的事件处理程序。
_clientMethod(hookStruct, out handle);
if (handle) return 1; //1:表示拦截键盘,return 退出
}
}
return CallNextHookEx(_hHookValue, nCode, wParam, lParam);
}
}
}
鼠标操作 + 逻辑处理
Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace MouseClicker
{
public partial class frmMain : Form
{
public frmMain()
{
InitializeComponent();
}
Timer timer = new Timer();
private bool updateLock = false;
//紧急按钮功能
private int exigencyButtonClick = 0;
private long exigencyLastDownTime = 0;
private int exigencyHoldTime = 1500;//紧急事件处理等待时长 两次紧急处理时间间隔不得大于 超过则无效
private bool runsLock = false;
private bool ResetFlag = false;
private KeyboardHookLib _keyboardHook = null;
private List keyFilter = null;
private EnumKeyType KeyType = EnumKeyType.none;
private Keys updateKey = Keys.None;
private delegate void Operate(EnumKeyType enumKey,Keys keyIndex);
// private List OperateList = new List();
// [System.Runtime.InteropServices.DllImport("user32")]
[DllImport("user32.dll")]
private static extern int mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);
[DllImport("user32.dll")]
private static extern int SetCursorPos(int x, int y);
///
/// 移动鼠标到指定的坐标点
///
//移动鼠标
const int MOUSEEVENTF_MOVE = 0x0001;
//模拟鼠标左键按下
const int MOUSEEVENTF_LEFTDOWN = 0x0002;
//模拟鼠标左键抬起
const int MOUSEEVENTF_LEFTUP = 0x0004;
//模拟鼠标右键按下
const int MOUSEEVENTF_RIGHTDOWN = 0x0008;
//模拟鼠标右键抬起
const int MOUSEEVENTF_RIGHTUP = 0x0010;
//模拟鼠标中键按下
const int MOUSEEVENTF_MIDDLEDOWN = 0x0020;
//模拟鼠标中键抬起
const int MOUSEEVENTF_MIDDLEUP = 0x0040;
//标示是否采用绝对坐标
const int MOUSEEVENTF_ABSOLUTE = 0x8000;
//模拟鼠标滚轮滚动操作,必须配合dwData参数
const int MOUSEEVENTF_WHEEL = 0x0800;
int screenHeight = Screen.PrimaryScreen.Bounds.Height;
int screenWidth = Screen.PrimaryScreen.Bounds.Width;
public void MoveMouseToPoint(Point p)
{
SetCursorPos(p.X, p.Y);
}
///
/// 设置鼠标的移动范围
///
public void SetMouseRectangle(Rectangle rectangle)
{
System.Windows.Forms.Cursor.Clip = rectangle;
}
///
/// 设置鼠标位于屏幕中心
///
public void SetMouseAtCenterScreen()
{
int winHeight = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Height;
int winWidth = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Width;
Point centerP = new Point(winWidth / 2, winHeight / 2);
MoveMouseToPoint(centerP);
}
[Obsolete]
private void Form1_Load(object sender, EventArgs e)
{
lbl_screen_whVal.Text = string.Format("{0} * {1}",screenWidth,screenHeight);
getMousePoint();
//开启钩子
_keyboardHook = new KeyboardHookLib();
_keyboardHook.InstallHook(this.OnKeyPress);
Console.WriteLine("钩子已开启..");
LoadHotKey();
}
[Obsolete]
private void LoadHotKey()
{
string StartHotKey = ConfigurationSettings.AppSettings["StartHotKey"].ToString();
string EndHotKey = ConfigurationSettings.AppSettings["EndHotKey"].ToString();
keyFilter = new List();
Keys keys = Keys.None;
Enum.TryParse(StartHotKey, true, out keys);
keyFilter.Add(keys);
if (string.IsNullOrEmpty(StartHotKey) || keys == Keys.None)
{
lbl_start_hot_key.Text = "未设置";
keyFilter.Remove(keys);
}
else
{
lbl_start_hot_key.Text = Enum.GetName(typeof(Keys),keys);
}
Enum.TryParse(EndHotKey, true, out keys);
keyFilter.Add(keys);
if (string.IsNullOrEmpty(EndHotKey) || keys == Keys.None)
{
lbl_end_hot_key.Text = "未设置";
keyFilter.Remove(keys);
}
else
{
lbl_end_hot_key.Text = Enum.GetName(typeof(Keys), keys);
}
if (keyFilter.Contains(Keys.None))
{
keyFilter.Remove(Keys.None);
}
}
private void SaveHotKey(EnumKeyType keyType,Keys key)
{
//获取Configuration对象
Configuration config = System.Configuration.ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
//写入元素的Value
int val = (int)key;
string startKey = config.AppSettings.Settings["StartHotKey"].Value;
string endKey = config.AppSettings.Settings["EndHotKey"].Value;
if (KeyType == EnumKeyType.start)
{
if (startKey.Equals(val.ToString()))
{
return;
} else if (endKey.Equals(val.ToString()))
{
config.AppSettings.Settings["EndHotKey"].Value ="";
}
config.AppSettings.Settings["StartHotKey"].Value = val.ToString();
}
else if (KeyType == EnumKeyType.end)
{
if (endKey.Equals(val.ToString()))
{
return;
}
else if (startKey.Equals(val.ToString()))
{
config.AppSettings.Settings["StartHotKey"].Value = "";
}
config.AppSettings.Settings["EndHotKey"].Value = val.ToString();
}
//一定要记得保存,写不带参数的config.Save()也可以
config.Save(ConfigurationSaveMode.Modified);
//刷新,否则程序读取的还是之前的值(可能已装入内存)
System.Configuration.ConfigurationManager.RefreshSection("appSettings");
}
private Point getMousePoint()
{
Point point = Control.MousePosition;
lbl_mouse_point.Text = string.Format("({0},{1})",point.X,point.Y);
if (updateLock == false)
{
txt_mouse_x_point.Text = point.X.ToString();
txt_mouse_y_point.Text = point.Y.ToString();
}
return point;
}
public void OnKeyPress(KeyboardHookLib.HookStruct hookStruct, out bool handle)
{
handle = false;
Keys key = (Keys)hookStruct.vkCode;
//处理紧急结束按键
if (key == Keys.Escape)
{
long nowTime = DateUtil.DateTimeToLongTimeStamp(DateTime.Now);
exigencyButtonClick++;
if ((exigencyLastDownTime + exigencyHoldTime) <= nowTime)
{
// 紧急处理时长 过长 清空本次紧急处理的记录
exigencyButtonClick = 0;
exigencyLastDownTime = 0;
}
exigencyLastDownTime = nowTime;
if (exigencyButtonClick >= 6)
{
exigencyButtonClick = 0;
if (isRun())
{
stopRun();
DialogResult result = MessageBox.Show("是否关闭连点器?", "提示", MessageBoxButtons.YesNo);
if (result == DialogResult.Yes)
{
Application.Exit();
}
}
else
{
Application.Exit();
}
}
}
if (KeyType!= EnumKeyType.none)
{
Operate operate = new Operate(resetHotKey);
operate(EnumKeyType.none, key);
return;
}
string StartHotKey = ConfigurationSettings.AppSettings["StartHotKey"].ToString();
string EndHotKey = ConfigurationSettings.AppSettings["EndHotKey"].ToString();
int startKey = 0;
int endKey = 0;
if (IsNumber(StartHotKey))
{
startKey = Convert.ToInt32(StartHotKey);
}
if (IsNumber(EndHotKey))
{
endKey = Convert.ToInt32(EndHotKey);
}
if (key == (Keys)startKey)
{
if (isRun() == false)
{
Run();
}
}
else if (key == (Keys)endKey)
{
if (isRun())
{
stopRun();
}
}
}
private bool isRun()
{
return runsLock;
}
private void stopRun()
{
Button button = GetControlOfName("btn_run", this.Controls) as Button;
if (button != null)
{
button.Text = "运行";
}
this.WindowState = FormWindowState.Normal; // 最小化
runsLock = false;
timer_clicker.Enabled = false;
timer_clicker.Stop();
}
private void Run()
{
Button button = GetControlOfName("btn_run", this.Controls) as Button;
if (button != null)
{
button.Text = "停止";
}
this.WindowState = FormWindowState.Minimized; // 最小化
runsLock = true;
timer_clicker.Enabled = true;
timer_clicker.Start();
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
}
private void timer_mouse_Tick(object sender, EventArgs e)
{
getMousePoint();
}
private void cbx_unLock_CheckedChanged(object sender, EventArgs e)
{
updateLock = cbx_unLock.Checked;
}
private void txt_mouse_x_point_TextChanged(object sender, EventArgs e)
{
MouseChangePoint(txt_mouse_x_point.Text, txt_mouse_y_point.Text);
}
private void MouseChangePoint(string pointX,string pointY)
{
string xpoint = pointX;
string ypoint = pointY;
if (updateLock == false)
return;
if (IsNumber(xpoint) == false && IsNumber(ypoint) == false)
return;
int x = 0;
int y = 0;
if (IsNumber(xpoint))
{
x = Convert.ToInt32(xpoint);
}
if (IsNumber(ypoint))
{
y = Convert.ToInt32(ypoint);
}
Point point = new Point(x,y);
// clearMousePoint();
// mouse_event(MOUSEEVENTF_MOVE, x, y, 0, 0);
SetMouseAtCenterScreen();//将鼠标置于屏幕中心
MoveMouseToPoint(point);
}
private bool IsNumber(string text)
{
if (string.IsNullOrEmpty(text))
return false;
try
{
Convert.ToInt32(text);
return true;
}
catch (Exception)
{
return false;
}
}
private void Form1_KeyPress(object sender, KeyPressEventArgs e)
{
/* if (e.KeyChar == 27)
{
Application.Exit();
}*/
}
private void txt_mouse_y_point_TextChanged(object sender, EventArgs e)
{
MouseChangePoint(txt_mouse_x_point.Text, txt_mouse_y_point.Text);
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
UnInstallHook();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
UnInstallHook();
}
private void UnInstallHook()
{
//移除钩子
if (_keyboardHook != null)
{
_keyboardHook.UninstallHook();
Console.WriteLine("钩子移除成功..");
}
}
private void btn_reset_start_Click(object sender, EventArgs e)
{
if (isSave())
{
saveHotKey();
return;
}
if (updateState() == false)
return;
resetHotKey(EnumKeyType.start);
}
private bool isSave()
{
return KeyType != EnumKeyType.none && updateKey != Keys.None;
}
private void btn_reset_end_Click(object sender, EventArgs e)
{
if (isSave())
{
saveHotKey();
return;
}
if (updateState() == false)
return;
resetHotKey(EnumKeyType.end);
}
private void saveHotKey()
{
if (KeyType != EnumKeyType.none && updateKey != Keys.None)
{
//
SaveHotKey(KeyType,updateKey);
resetState(KeyType);
LoadHotKey();
}
}
private Control GetControlOfName(string controlName, Control.ControlCollection containChildControlWindow)
{
if (string.IsNullOrEmpty(controlName)) return null;
foreach (Control item in containChildControlWindow)
{
if (item.Name.Equals(controlName))
{
return item;
}
}
return null;
}
private bool updateState()
{
if (ResetFlag)
return !ResetFlag;
ResetFlag = !ResetFlag;
return ResetFlag;
}
private void resetState(EnumKeyType type)
{
ResetFlag = false;
KeyType = EnumKeyType.none;
updateKey = Keys.None;
Button button = GetControlOfName(string.Format("{0}", type == EnumKeyType.start ? "btn_reset_start" : "btn_reset_end"), this.Controls) as Button;
if (button == null)
return;
button.Text = "重置";
}
private void resetHotKey(EnumKeyType type,Keys keys = Keys.None)
{
if (type != EnumKeyType.none)
{
DialogResult result = MessageBox.Show(string.Format("是否重置{0}连点器按键?", type == EnumKeyType.start ? "开启" : "结束"), "提示", MessageBoxButtons.YesNo);
if (result == DialogResult.Yes)
{
Button button = GetControlOfName(string.Format("{0}",type == EnumKeyType.start ? "btn_reset_start" : "btn_reset_end"), this.Controls) as Button;
if (button == null)
{
return;
}
Label label = GetControlOfName(string.Format("{0}", type == EnumKeyType.start ? "lbl_start_hot_key" : "lbl_end_hot_key"), this.Controls) as Label;
if (label == null)
{
return;
}
label.Text = "未设置";
button.Text = "保存";
KeyType = type;
}
else
{
resetState(type);
}
}
else
{
if (KeyType != EnumKeyType.none)
{
updateKey = keys;
Label label = GetControlOfName(string.Format("{0}", KeyType == EnumKeyType.start ? "lbl_start_hot_key" : "lbl_end_hot_key"), this.Controls) as Label;
if (label == null)
{
return;
}
label.Text = Enum.GetName(typeof(Keys),keys);
}
else
{
resetState(KeyType);
}
}
}
private void nud_speed_check_ValueChanged(object sender, EventArgs e)
{
updateTick();
}
private void updateTick()
{
if (isRun())
{
stopRun();//暂停一下
if (nud_speed_check.Value <= 0 || nud_speed_check.Value >= 9999999)
{
nud_speed_check.Value = 10000;
}
timer_clicker.Interval = (int)nud_speed_check.Value;
Run();//继续
}
else
{
if (nud_speed_check.Value <= 0 || nud_speed_check.Value >= 9999999)
{
nud_speed_check.Value = 10000;
}
timer_clicker.Interval = (int)nud_speed_check.Value;
}
}
public int number = 0;
private void timer_clicker_Tick(object sender, EventArgs e)
{
if (rdb_left.Checked)
{
mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, Convert.ToInt32(txt_mouse_x_point.Text), Convert.ToInt32(txt_mouse_y_point.Text), 0, 0);
}
else
{
mouse_event(MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_RIGHTUP, Convert.ToInt32(txt_mouse_x_point.Text), Convert.ToInt32(txt_mouse_y_point.Text), 0, 0);
}
//Console.WriteLine($"开始点击屏幕{number++}!!!!");
}
private void nud_speed_check_FontChanged(object sender, EventArgs e)
{
}
private void nud_speed_check_KeyPress(object sender, KeyPressEventArgs e)
{
updateTick();
}
private void btn_run_Click(object sender, EventArgs e)
{
if (isRun() == false)
{
Run();
}
else
{
stopRun();
}
}
private void rdb_right_CheckedChanged(object sender, EventArgs e)
{
if (rdb_right.Checked)
{
rdb_left.Checked = false;
}
else
{
rdb_left.Checked = true;
}
}
private void rdb_left_CheckedChanged(object sender, EventArgs e)
{
if (rdb_left.Checked)
{
rdb_right.Checked = false;
}
else
{
rdb_right.Checked = true;
}
}
}
public enum EnumKeyType
{
none,
start,
end,
}
}
时间操作工具类
DateUtil.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MouseClicker
{
public class DateUtil
{
///
/// 时间戳计时开始时间
///
private static DateTime timeStampStartTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
///
/// DateTime转换为10位时间戳(单位:秒)
///
/// DateTime
/// 10位时间戳(单位:秒)
public static long DateTimeToTimeStamp(DateTime dateTime)
{
return (long)(dateTime.ToUniversalTime() - timeStampStartTime).TotalSeconds;
}
///
/// DateTime转换为13位时间戳(单位:毫秒)
///
/// DateTime
/// 13位时间戳(单位:毫秒)
public static long DateTimeToLongTimeStamp(DateTime dateTime)
{
return (long)(dateTime.ToUniversalTime() - timeStampStartTime).TotalMilliseconds;
}
///
/// 10位时间戳(单位:秒)转换为DateTime
///
/// 10位时间戳(单位:秒)
/// DateTime
public static DateTime TimeStampToDateTime(long timeStamp)
{
return timeStampStartTime.AddSeconds(timeStamp).ToLocalTime();
}
///
/// 13位时间戳(单位:毫秒)转换为DateTime
///
/// 13位时间戳(单位:毫秒)
/// DateTime
public static DateTime LongTimeStampToDateTime(long longTimeStamp)
{
return timeStampStartTime.AddMilliseconds(longTimeStamp).ToLocalTime();
}
}
}
配置文件:
App.config