最早这个我是在20年前用vb写过,当时只是为了自己玩游戏写的,本文将详细介绍如何使用C#开发一个鼠标操作录制和回放工具。这个工具可以记录用户的鼠标移动和点击操作,并能够保存、加载和回放这些操作。
C#public class MouseEvent
{
public int X { get; set; }
public int Y { get; set; }
public bool IsLeftClick { get; set; }
public MouseEvent(int x, int y, bool isLeftClick)
{
X = x;
Y = y;
IsLeftClick = isLeftClick;
}
}
这个类用于存储单个鼠标事件,包括鼠标位置和是否左键点击。
这是主窗体类,包含了所有的UI控件和核心逻辑。
C#private void btnStartRecord_Click(object sender, EventArgs e)
{
if (!isRecording)
{
events.Clear();
isRecording = true;
btnStartRecord.Text = "Stop Recording";
recordTimer = new System.Windows.Forms.Timer();
recordTimer.Interval = 100;
recordTimer.Tick += Timer_Tick;
recordTimer.Start();
}
else
{
StopRecording();
}
}
当点击"开始录制"按钮时,我们清空之前的事件列表,设置录制状态为true,并启动一个计时器每100毫秒记录一次鼠标状态。
C#private void Timer_Tick(object sender, EventArgs e)
{
if (isRecording)
{
Point currentPosition = Cursor.Position;
bool isLeftButtonPressed = (Control.MouseButtons & MouseButtons.Left) != 0;
events.Add(new MouseEvent(currentPosition.X, currentPosition.Y, isLeftButtonPressed));
}
}
在计时器的Tick事件中,我们获取当前鼠标位置和左键状态,并将其添加到事件列表中。
C#private void btnSave_Click(object sender, EventArgs e)
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "Text files (*.txt)|*.txt";
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
using (StreamWriter writer = new StreamWriter(saveFileDialog.FileName))
{
foreach (MouseEvent mouseEvent in events)
{
writer.WriteLine($"{mouseEvent.X},{mouseEvent.Y},{mouseEvent.IsLeftClick}");
}
}
}
}
保存功能将录制的鼠标事件以CSV格式写入文本文件。
C#private void btnLoad_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "Text files (*.txt)|*.txt";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
events.Clear();
using (StreamReader reader = new StreamReader(openFileDialog.FileName))
{
string line;
while ((line = reader.ReadLine()) != null)
{
string[] parts = line.Split(',');
if (parts.Length == 3)
{
int x = int.Parse(parts[0]);
int y = int.Parse(parts[1]);
bool isLeftClick = bool.Parse(parts[2]);
events.Add(new MouseEvent(x, y, isLeftClick));
}
}
}
}
}
加载功能从文本文件读取CSV格式的鼠标事件,并重建事件列表。
C#private void btnPlay_Click(object sender, EventArgs e)
{
if (!isPlaying)
{
loopCount = (int)numericUpDownLoopCount.Value;
currentLoop = 0;
isPlaying = true;
btnPlay.Text = "停止";
Thread playThread = new Thread(PlayEvents);
playThread.Start();
}
else
{
StopPlaying();
}
}
当点击"播放"按钮时,我们设置循环次数,创建一个新线程来执行回放操作。
C#private void PlayEvents()
{
while (isPlaying && (loopCount == -1 || currentLoop < loopCount))
{
currentLoop++;
UpdateLoopCountDisplay();
foreach (MouseEvent mouseEvent in events)
{
if (!isPlaying) break;
SetCursorPos(mouseEvent.X, mouseEvent.Y);
if (mouseEvent.IsLeftClick)
{
mouse_event(MOUSEEVENTF_LEFTDOWN, mouseEvent.X, mouseEvent.Y, 0, 0);
mouse_event(MOUSEEVENTF_LEFTUP, mouseEvent.X, mouseEvent.Y, 0, 0);
}
Thread.Sleep(100);
}
if (loopCount != -1 && currentLoop >= loopCount)
{
break;
}
}
// 回放结束后更新UI
this.Invoke((MethodInvoker)delegate
{
isPlaying = false;
btnPlay.Text = "播放";
currentLoop = 0;
UpdateLoopCountDisplay();
});
}
回放过程中,我们遍历事件列表,设置鼠标位置并模拟点击操作。支持无限循环或指定次数的循环回放。
为了方便用户随时停止录制或回放,我们实现了一个全局热键(Esc键)来停止所有操作。
C#private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);
if ((Keys)vkCode == Keys.Escape)
{
Application.OpenForms[0].Invoke(new Action(() => ((Form1)Application.OpenForms[0]).StopAll()));
}
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
这个回调函数检测Esc键的按下,并调用StopAll方法停止所有操作。
C#using System.Diagnostics;
using System.Runtime.InteropServices;
namespace MouseRecorderAndPlayer
{
public partial class Form1 : Form
{
private List<MouseEvent> events = new List<MouseEvent>();
private bool isRecording = false;
private bool isPlaying = false;
private System.Windows.Forms.Timer recordTimer;
private int loopCount = -1; // -1 表示无限循环
private int currentLoop = 0;
// 添加全局键盘钩子相关的字段
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private static LowLevelKeyboardProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
public Form1()
{
InitializeComponent();
// 在构造函数中设置键盘钩子
_hookID = SetHook(_proc);
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
// 在窗体关闭时取消键盘钩子
UnhookWindowsHookEx(_hookID);
base.OnFormClosing(e);
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("user32.dll")]
static extern bool SetCursorPos(int X, int Y);
[DllImport("user32.dll")]
public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);
private const int MOUSEEVENTF_LEFTDOWN = 0x02;
private const int MOUSEEVENTF_LEFTUP = 0x04;
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
}
}
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);
if ((Keys)vkCode == Keys.Escape)
{
Application.OpenForms[0].Invoke(new Action(() => ((Form1)Application.OpenForms[0]).StopAll()));
}
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
private void StopAll()
{
if (isRecording)
{
StopRecording();
}
if (isPlaying)
{
StopPlaying();
}
}
private void StopRecording()
{
isRecording = false;
btnStartRecord.Text = "Start Recording";
recordTimer.Stop();
}
private void StopPlaying()
{
isPlaying = false;
btnPlay.Text = "播放";
}
private void btnStartRecord_Click(object sender, EventArgs e)
{
if (!isRecording)
{
events.Clear();
isRecording = true;
btnStartRecord.Text = "Stop Recording";
recordTimer = new System.Windows.Forms.Timer();
recordTimer.Interval = 100;
recordTimer.Tick += Timer_Tick;
recordTimer.Start();
}
else
{
StopRecording();
}
}
private void Timer_Tick(object sender, EventArgs e)
{
if (isRecording)
{
Point currentPosition = Cursor.Position;
bool isLeftButtonPressed = (Control.MouseButtons & MouseButtons.Left) != 0;
events.Add(new MouseEvent(currentPosition.X, currentPosition.Y, isLeftButtonPressed));
}
}
private void btnSave_Click(object sender, EventArgs e)
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "Text files (*.txt)|*.txt";
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
using (StreamWriter writer = new StreamWriter(saveFileDialog.FileName))
{
foreach (MouseEvent mouseEvent in events)
{
writer.WriteLine($"{mouseEvent.X},{mouseEvent.Y},{mouseEvent.IsLeftClick}");
}
}
}
}
private void btnLoad_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "Text files (*.txt)|*.txt";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
events.Clear();
using (StreamReader reader = new StreamReader(openFileDialog.FileName))
{
string line;
while ((line = reader.ReadLine()) != null)
{
string[] parts = line.Split(',');
if (parts.Length == 3)
{
int x = int.Parse(parts[0]);
int y = int.Parse(parts[1]);
bool isLeftClick = bool.Parse(parts[2]);
events.Add(new MouseEvent(x, y, isLeftClick));
}
}
}
}
}
private void btnPlay_Click(object sender, EventArgs e)
{
if (!isPlaying)
{
loopCount = (int)numericUpDownLoopCount.Value;
currentLoop = 0;
isPlaying = true;
btnPlay.Text = "播放";
Thread playThread = new Thread(PlayEvents);
playThread.Start();
}
else
{
StopPlaying();
}
}
private void PlayEvents()
{
while (isPlaying && (loopCount == -1 || currentLoop < loopCount))
{
currentLoop++;
UpdateLoopCountDisplay();
foreach (MouseEvent mouseEvent in events)
{
if (!isPlaying) break;
SetCursorPos(mouseEvent.X, mouseEvent.Y);
if (mouseEvent.IsLeftClick)
{
mouse_event(MOUSEEVENTF_LEFTDOWN, mouseEvent.X, mouseEvent.Y, 0, 0);
mouse_event(MOUSEEVENTF_LEFTUP, mouseEvent.X, mouseEvent.Y, 0, 0);
}
Thread.Sleep(100);
}
if (loopCount != -1 && currentLoop >= loopCount)
{
break;
}
}
this.Invoke((MethodInvoker)delegate
{
isPlaying = false;
btnPlay.Text = "播放";
currentLoop = 0;
UpdateLoopCountDisplay();
});
}
private void UpdateLoopCountDisplay()
{
this.Invoke((MethodInvoker)delegate
{
if (loopCount == -1)
{
lblLoopStatus.Text = $"Loop: {currentLoop} (Infinite)";
}
else
{
lblLoopStatus.Text = $"Loop: {currentLoop}/{loopCount}";
}
});
}
}
public class MouseEvent
{
public int X { get; set; }
public int Y { get; set; }
public bool IsLeftClick { get; set; }
public MouseEvent(int x, int y, bool isLeftClick)
{
X = x;
Y = y;
IsLeftClick = isLeftClick;
}
}
}
本文详细介绍了如何使用C#实现一个鼠标操作录制和回放工具。通过使用Windows API和C#的多线程功能,我们实现了鼠标事件的捕获、保存、加载和回放。此外,我们还添加了全局热键控制,提高了工具的使用便利性。这个工具可以在多种场景下使用,如自动化测试、重复性操作模拟等。
本文作者:rick
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!