编辑
2025-02-09
C# 应用
00
请注意,本文编写于 86 天前,最后修改于 86 天前,其中某些信息可能已经过时。

目录

1. 简介
2. 核心功能
3. 主要类和结构
3.1 MouseEvent 类
3.2 Form1 类
4. 录制功能实现
4.1 开始录制
4.2 记录鼠标状态
5. 保存和加载功能
5.1 保存录制的操作
5.2 加载保存的操作
6. 回放功能实现
6.1 开始回放
6.2 执行回放
7. 全局热键控制
8.完整代码
9 总结

1. 简介

最早这个我是在20年前用vb写过,当时只是为了自己玩游戏写的,本文将详细介绍如何使用C#开发一个鼠标操作录制和回放工具。这个工具可以记录用户的鼠标移动和点击操作,并能够保存、加载和回放这些操作。

2. 核心功能

  • 录制鼠标移动和点击
  • 保存和加载录制的操作
  • 回放录制的操作
  • 支持循环回放
  • 全局热键控制

3. 主要类和结构

3.1 MouseEvent 类

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; } }

这个类用于存储单个鼠标事件,包括鼠标位置和是否左键点击。

3.2 Form1 类

这是主窗体类,包含了所有的UI控件和核心逻辑。

image.png

4. 录制功能实现

4.1 开始录制

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毫秒记录一次鼠标状态。

4.2 记录鼠标状态

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事件中,我们获取当前鼠标位置和左键状态,并将其添加到事件列表中。

5. 保存和加载功能

5.1 保存录制的操作

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格式写入文本文件。

5.2 加载保存的操作

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格式的鼠标事件,并重建事件列表。

6. 回放功能实现

6.1 开始回放

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(); } }

当点击"播放"按钮时,我们设置循环次数,创建一个新线程来执行回放操作。

6.2 执行回放

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(); }); }

回放过程中,我们遍历事件列表,设置鼠标位置并模拟点击操作。支持无限循环或指定次数的循环回放。

7. 全局热键控制

为了方便用户随时停止录制或回放,我们实现了一个全局热键(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方法停止所有操作。

8.完整代码

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; } } }

9 总结

本文详细介绍了如何使用C#实现一个鼠标操作录制和回放工具。通过使用Windows API和C#的多线程功能,我们实现了鼠标事件的捕获、保存、加载和回放。此外,我们还添加了全局热键控制,提高了工具的使用便利性。这个工具可以在多种场景下使用,如自动化测试、重复性操作模拟等。

本文作者:rick

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!