Windows 原生风格的鼠标自动操作工具
平时办公、测试或者玩游戏时,总有一些重复的鼠标点击动作让人厌烦:比如每隔几分钟就要点一下“刷新”、在固定位置进行一系列右键操作,或者为了测试某个软件要反复模拟用户点击。市面上虽然有不少“按键精灵”类工具,但要么体积臃肿,要么需要付费,要么界面不符合我的审美。于是,我决定用 Python 自己写一个——既满足原生 Windows 风格,又能录制、保存、循环回放鼠标操作,而且打包后只有一个 exe 文件,不依赖任何外部环境。
二、技术选型与环境
我用的是 Python 3.8+,依赖库:
pyautogui:模拟鼠标移动和点击pynput:监听鼠标事件(按下/释放)tkinter+ttk:GUI 框架,借助vista主题呈现原生风格
安装命令:
pip install pyautogui pynput
(打包时再加 pyinstaller)
三、项目骨架与数据持久化(防丢数据的核心)
首先解决一个关键问题:打包成 exe 后,配置文件该存哪?
如果直接用 __file__,打包后它指向临时解压目录,退出即销毁,你的宏就全没了。所以我们需要一个函数,返回 exe 真正所在的目录:
def get_app_dir():
if getattr(sys, "frozen", False):
return os.path.dirname(os.path.abspath(sys.executable))
return os.path.dirname(os.path.abspath(__file__))
APP_DIR = get_app_dir()
MACROS_FILE = os.path.join(APP_DIR, "macros.json")
同时,为了避免旧版单文件 macro.json 被遗忘,我做了自动迁移:
if os.path.exists(OLD_MACRO_FILE):
with open(OLD_MACRO_FILE) as f:
old_steps = json.load(f)
self.macros = {"默认操作": old_steps}
self._save_macros_to_disk()
这样用户升级时数据无缝过渡。
另外,打包时加了 -w(无控制台),sys.stdout 可能为 None,一调 print 就崩。所以我做了保护:
if sys.stdout is None:
sys.stdout = open(os.devnull, "w")
if sys.stderr is None:
sys.stderr = open(os.devnull, "w")
四、界面布局:原生主题 + 置顶 + 左右分栏
GUI 我用 ttk 并强制加载 vista 主题(Win10/11 原生风格):
style = ttk.Style(self.root)
available = style.theme_names()
for theme in ("vista", "xpnative", "winnative", "clam"):
if theme in available:
style.theme_use(theme)
break
窗口左上角是标题,右上角是“窗口置顶”复选框,通过 attributes("-topmost", ...) 控制。
主体采用左右两栏(grid 布局):
- 左栏:
LabelFrame包裹Listbox+ 三个按钮(加载/重命名/删除) - 右栏:
LabelFrame包裹录制控制、当前步骤Listbox、编辑按钮和“保存操作…”
底部是循环控制区,包含间隔输入、开始/停止、倒计时标签、循环次数、红色运行警告。
所有控件都使用系统字体 Segoe UI,并定义了多种 style 来美化标题和倒计时。
关键代码片段(构建右侧录制控制):
rec_frame = ttk.Frame(frame)
rec_frame.pack(fill="x", pady=(0, 8))
self.btn_start_rec = ttk.Button(rec_frame, text="开始录制", command=self.start_recording)
self.btn_start_rec.pack(side="left", padx=(0, 6))
self.btn_stop_rec = ttk.Button(rec_frame, text="停止录制", command=self.stop_recording, state="disabled")
self.btn_stop_rec.pack(side="left")
步骤列表用 Listbox 配合 Consolas 字体显示坐标和间隔,整齐对齐。
五、录制功能:监听、过滤、双击合并
点击“开始录制”后,会清空步骤列表,启动 pynput 监听器:
self.listener = mouse.Listener(on_click=self._on_click)
self.listener.start()
在 _on_click 回调中,只处理 pressed=True(按下瞬间),并判断坐标是否在面板窗口内,是则忽略:
def _is_click_on_panel(self, x, y):
rx = self.root.winfo_rootx()
ry = self.root.winfo_rooty()
rw = self.root.winfo_width()
rh = self.root.winfo_height()
return rx <= x <= rx + rw and ry <= y <= ry + rh
有效点击后,计算与上次点击的时间差 delay,并检查是否符合双击条件(坐标距离 ≤ 6 像素,时间差 ≤ 0.35 秒,同一按键,且上一步是单次点击)。如果满足,就把上一步的 click_type 改为 "double",本次不新增步骤;否则新建一个步骤:
if same_pos and same_btn and fast_enough and last.get("click_type", "single") == "single":
last["click_type"] = "double"
self._last_click_time = now
self.root.after(0, self._update_last_step_in_listbox)
return
step = {"x": x, "y": y, "button": btn_name, "delay": delay, "click_type": "single"}
self.steps.append(step)
self.root.after(0, self._append_step_to_listbox, step, len(self.steps))
注意这里用 root.after(0, ...) 把 UI 更新放回主线程,避免界面卡顿。
六、多组操作管理:保存、加载、重命名、删除
macros 是一个字典,键为操作名,值为步骤列表(深拷贝后存储)。
保存:调用 save_current_as,弹出输入框,如果重名则询问覆盖,然后写入 JSON:
self.macros[name] = json.loads(json.dumps(self.steps))
self._save_macros_to_disk()
加载:从字典中深拷贝步骤到当前编辑区:
self.steps = json.loads(json.dumps(self.macros[name]))
self.current_macro_name = name
self._refresh_listbox()
重命名:删除旧键,插入新键,刷新列表。
删除:确认后 del self.macros[name]。
每个操作后都调用 _save_macros_to_disk() 持久化。
七、循环回放:多线程、倒计时、安全停止
回放不能阻塞 UI,所以我用 threading.Thread 执行循环。开始前先检查步骤是否存在,并读取间隔分钟数转为秒。
核心循环:
def _play_loop(self, interval_seconds):
while not self._stop_play_flag.is_set():
self.root.after(0, self.countdown_var.set, "执行中...")
self._run_once() # 执行一次所有步骤
if self._stop_play_flag.is_set():
return
self.loop_count += 1
self.root.after(0, self.loop_count_var.set, f"{self.loop_count} 次")
# 倒计时(每秒更新一次)
waited = 0.0
while waited < interval_seconds:
if self._stop_play_flag.is_set():
self.root.after(0, self.countdown_var.set, "--:--")
return
remaining = interval_seconds - waited
mm = int(remaining) // 60
ss = int(remaining) % 60
self.root.after(0, self.countdown_var.set, f"{mm:02d}:{ss:02d}")
time.sleep(0.2)
waited += 0.2
执行单次操作 _run_once 会遍历所有步骤,移动鼠标(pyautogui.moveTo)、根据 click_type 单击或双击(pyautogui.click(clicks=2)),并在状态栏更新进度。
停止时设置 _stop_play_flag,线程检测到立即退出,并重置 UI 状态。
安全细节:我把 pyautogui.FAILSAFE = False,因为默认的“左上角中断”在倒计时等待时容易被误触,影响用户体验。
八、打包成单 exe(带图标)
使用 PyInstaller,命令如下:
pyinstaller -F -w --icon=mouse_icon.ico --add-data "mouse_icon.ico;." auto_click_panel.py
-F:单文件-w:无控制台--icon:指定 exe 图标--add-data:将图标文件打包进 exe,运行时通过sys._MEIPASS访问
在代码中,我这样加载图标(兼顾开发环境和打包环境):
meipass = getattr(sys, "_MEIPASS", None)
candidates = []
if meipass:
candidates.append(os.path.join(meipass, "mouse_icon.ico"))
candidates.append(os.path.join(APP_DIR, "mouse_icon.ico"))
for c in candidates:
if os.path.exists(c):
icon_path = c
break
if icon_path:
self.root.iconbitmap(icon_path)
这样不管你是直接跑 .py 还是运行打包后的 exe,图标都能正常显示。
九、最终效果与使用心得
最终程序运行起来就是一个清爽的 Windows 风格窗口。录制时面板内点击被自动忽略,回放时红色大字警告防止误操作,倒计时清晰可见。所有宏保存在 exe 同目录的 macros.json 中,拷贝到其他电脑也能直接用。
我平时用它来定时刷新网页、重复测试 UI、挂机采集游戏资源,稳定可靠。代码总共不到 500 行,注释详尽,易于扩展(比如加入键盘录制、条件判断等)。
十、完整代码
由于篇幅,这里不再全文粘贴(你可在原问题中找到完整源码),但上面的代码片段已经覆盖了所有核心逻辑。你只需将它们组合起来,并参考开头的完整文件结构,就能跑起来。
Click_Mouse
最后的话
通过这篇文章,希望你不仅学会了怎么用,更明白了它是如何一步步实现的。如果你有任何改进想法(比如增加快捷键、热键启动、多语言支持),欢迎在评论区交流。动手实践,你也能写出属于自己的生产力工具!
本文所有代码均在 Windows 10/11 上测试通过,Python 3.8+ 环境。
文章链接:https://www.lilianhua.com/windows-native-style-mouse-automatic-operation-tool.html
