总结
这个Python程序是一个基于Tkinter的GUI应用程序,用于录制和回放用户的鼠标和键盘操作。主要功能包括:
-
录制功能:
- 用户可以选择录制哪些类型的动作(如单击、双击、拖动、滚动、按键、移动)。
- 通过按
F1
键可以开始或停止录制。 - 录制的动作会显示在一个列表框中。
-
回放功能:
- 用户可以通过输入重复次数、延迟时间和间隔时间来配置回放参数。
- 通过按
F2
键可以开始回放录制的动作。 - 通过按
F3
键可以停止正在进行的回放。 - 回放的状态会在状态面板中显示。
-
保存和加载功能:
- 用户可以将录制的动作保存到文件中,默认文件名为“录制自动点击脚本.txt”。
- 用户可以从文件中加载之前保存的动作。
- 通过按
F4
键可以保存录制的动作。 - 通过按
F5
键可以加载录制的动作。
-
其他功能:
- 用户可以通过按
F6
键启动循环点击模式。 - 用户可以通过按
F7
键清除当前所有的录制动作。
- 用户可以通过按
主要组件
- ActionManager: 负责管理录制的动作,包括添加、获取、清除动作以及保存和加载动作到文件。
- RecordingController: 控制录制过程,监听鼠标的移动、点击、滚动事件以及键盘的按键事件,并记录相应的动作。
- Controller: 控制回放过程,负责执行录制的动作并处理相关的状态更新。
- RecordingPlaybackTab: 构建GUI界面,包含录制、回放、保存、加载等功能的控件,并绑定相应的事件处理函数。
这个程序适合需要自动化测试、演示或其他需要模拟用户操作的场景。
python">import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from pynput import mouse, keyboard
from pynput.mouse import Listener as MouseListener
from pynput.keyboard import Listener as KeyboardListener
from threading import Thread
import time
import random # 导入 random 模块class ActionManager:def __init__(self):self.actions = []def add_action(self, action):self.actions.append(action)def get_actions(self):return self.actionsdef clear_actions(self):self.actions.clear()def save_to_file(self, file_path):with open(file_path, 'w') as file:for action in self.actions:file.write(str(action) + '\n')def load_from_file(self, file_path):self.actions.clear()with open(file_path, 'r') as file:for line in file:action = eval(line.strip())self.actions.append(action)class RecordingController:def __init__(self, action_manager, update_action_listbox, cycle_clicks, toggle_recording,record_single_click, record_double_click, record_dragging, record_scroll, record_key_press, record_mouse_move):self.action_manager = action_managerself.update_action_listbox = update_action_listboxself.cycle_clicks = cycle_clicksself.toggle_recording = toggle_recordingself.recording = Falseself.mouse_listener = Noneself.keyboard_listener = Noneself.mouse = mouse.Controller()self.keyboard = keyboard.Controller()self.drag_start_pos = Noneself.dragging = Falseself.record_single_click = record_single_clickself.record_double_click = record_double_clickself.record_dragging = record_draggingself.record_scroll = record_scrollself.record_key_press = record_key_pressself.record_mouse_move = record_mouse_movedef on_move(self, x, y):if self.recording and self.dragging and self.record_dragging.get():action = {"type": "mouse_move", "position": (x, y)}self.action_manager.add_action(action)self.update_action_listbox(action)elif self.recording and self.record_mouse_move.get():action = {"type": "mouse_move", "position": (x, y)}self.action_manager.add_action(action)self.update_action_listbox(action)def on_click(self, x, y, button, pressed):if self.recording:if not pressed and self.dragging and self.record_dragging.get():self.dragging = Falseaction = {"type": "mouse_release", "position": (x, y), "button": str(button)}self.action_manager.add_action(action)self.update_action_listbox(action)elif pressed and not self.dragging and self.record_dragging.get():self.dragging = Trueself.drag_start_pos = (x, y)action = {"type": "mouse_press", "position": (x, y), "button": str(button)}self.action_manager.add_action(action)self.update_action_listbox(action)else:action = {"type": "mouse_click", "position": (x, y), "button": str(button), "pressed": pressed}if self.record_single_click.get() or (not pressed and self.record_double_click.get()):self.action_manager.add_action(action)self.update_action_listbox(action)def on_scroll(self, x, y, dx, dy):if self.recording and self.record_scroll.get():action = {"type": "mouse_scroll", "position": (x, y), "dx": dx, "dy": dy}self.action_manager.add_action(action)self.update_action_listbox(action)def on_press(self, key):try:k = key.charexcept AttributeError:k = f"{key}"if self.recording and self.record_key_press.get():action = {"type": "key_press", "key": k}self.action_manager.add_action(action)self.update_action_listbox(action)if key == keyboard.Key.f1:self.toggle_recording()elif key == keyboard.Key.f2:self.start_playing()elif key == keyboard.Key.f3:self.stop_playing()elif key == keyboard.Key.f4:self.save_recorded_actions()elif key == keyboard.Key.f5:self.load_recorded_actions()elif key == keyboard.Key.f6:self.cycle_clicks()elif key == keyboard.Key.f7:self.clear_recorded_actions()def start_recording(self):if self.recording:returnself.recording = Trueself.mouse_listener = MouseListener(on_move=self.on_move, on_click=self.on_click, on_scroll=self.on_scroll)self.keyboard_listener = KeyboardListener(on_press=self.on_press)self.mouse_listener.start()self.keyboard_listener.start()def stop_recording(self):if not self.recording:returnself.recording = Falseself.mouse_listener.stop()self.keyboard_listener.stop()class Controller:def __init__(self, action_manager, recording_controller, update_action_listbox, update_status_panel):self.action_manager = action_managerself.recording_controller = recording_controllerself.update_action_listbox = update_action_listboxself.update_status_panel = update_status_panelself.playing = Falseself.thread = Nonedef start_playing(self, repeat_times, min_interval, max_interval, delay):if self.playing:returnself.playing = Trueself.thread = Thread(target=self._play_actions, args=(repeat_times, min_interval, max_interval, delay))self.thread.start()def _play_actions(self, repeat_times, min_interval, max_interval, delay):actions = self.action_manager.get_actions()if not actions:self.update_status_panel("没有可用的动作进行播放。")self.playing = Falsereturntime.sleep(delay / 1000) # Delay before starting playbackfor _ in range(repeat_times):if not self.playing:breakfor action in actions:if not self.playing:breakinterval = random.uniform(min_interval, max_interval) # Generate a random interval within the specified rangeif action["type"] == "mouse_click":button = getattr(mouse.Button, action["button"].split('.')[1]) if '.' in action["button"] else mouse.Button.leftself.recording_controller.mouse.position = action["position"]if action["pressed"]:self.recording_controller.mouse.press(button)else:self.recording_controller.mouse.release(button)elif action["type"] == "mouse_scroll":self.recording_controller.mouse.position = action["position"]self.recording_controller.mouse.scroll(action["dx"], action["dy"])elif action["type"] == "key_press":key = action["key"]if key.startswith('Key'):key = getattr(keyboard.Key, key.split('.')[1])self.recording_controller.keyboard.press(key)self.recording_controller.keyboard.release(key)elif action["type"] == "mouse_move":self.recording_controller.mouse.position = action["position"]elif action["type"] == "mouse_press":button = getattr(mouse.Button, action["button"].split('.')[1]) if '.' in action["button"] else mouse.Button.leftself.recording_controller.mouse.position = action["position"]self.recording_controller.mouse.press(button)elif action["type"] == "mouse_release":button = getattr(mouse.Button, action["button"].split('.')[1]) if '.' in action["button"] else mouse.Button.leftself.recording_controller.mouse.position = action["position"]self.recording_controller.mouse.release(button)time.sleep(interval)self.playing = Falseself.update_status_panel("回放已完成")def stop_playing(self):self.playing = Falseif self.thread and self.thread.is_alive():self.thread.join()class RecordingPlaybackTab(ttk.Frame):def __init__(self, parent):super().__init__(parent)self.parent = parentself.repeat_times = 1 # 默认重复次数1次self.delay = 0 # 默认延迟为0msself.min_interval = 0.01 # 默认最小间隔时间为0.1秒self.max_interval = 0.1 # 默认最大间隔时间为1秒self.action_manager = ActionManager()self.record_single_click = tk.BooleanVar(value=False)self.record_double_click = tk.BooleanVar(value=False)self.record_dragging = tk.BooleanVar(value=False)self.record_scroll = tk.BooleanVar(value=False)self.record_key_press = tk.BooleanVar(value=False)self.record_mouse_move = tk.BooleanVar(value=False)self.recording_controller = RecordingController(self.action_manager,self.update_action_listbox,self.cycle_clicks,self.toggle_recording,self.record_single_click,self.record_double_click,self.record_dragging,self.record_scroll,self.record_key_press,self.record_mouse_move)self.controller = Controller(self.action_manager, self.recording_controller, self.update_action_listbox, self.update_status_panel)self.create_widgets()def create_widgets(self):# 设置主框架main_frame = ttk.Frame(self)main_frame.pack(fill=tk.BOTH, expand=True)# 录制和回放控制区control_frame = ttk.Frame(main_frame)control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=10, pady=10)# 重复次数repeat_times_frame = ttk.Frame(control_frame)repeat_times_label = ttk.Label(repeat_times_frame, text="重复次数:")self.repeat_times_entry = ttk.Entry(repeat_times_frame, width=5)self.repeat_times_entry.insert(0, str(self.repeat_times)) # 默认1次repeat_times_label.grid(row=0, column=0, padx=(10, 0), pady=(10, 0))self.repeat_times_entry.grid(row=0, column=1, padx=(0, 10), pady=(10, 0))repeat_times_frame.pack(padx=10, fill=tk.X)# 延迟delay_frame = ttk.Frame(control_frame)delay_label = ttk.Label(delay_frame, text="延迟(ms):")self.delay_entry = ttk.Entry(delay_frame, width=5)self.delay_entry.insert(0, str(self.delay)) # 默认0msdelay_label.grid(row=0, column=0, padx=(10, 0), pady=(10, 0))self.delay_entry.grid(row=0, column=1, padx=(0, 10), pady=(10, 0))delay_frame.pack(padx=10, fill=tk.X)# 最小间隔时间min_interval_frame = ttk.Frame(control_frame)min_interval_label = ttk.Label(min_interval_frame, text="最小间隔时间(秒):")self.min_interval_entry = ttk.Entry(min_interval_frame, width=5)self.min_interval_entry.insert(0, str(self.min_interval)) # 默认值0.1秒min_interval_label.grid(row=0, column=0, padx=(10, 0), pady=(10, 0))self.min_interval_entry.grid(row=0, column=1, padx=(0, 10), pady=(10, 0))min_interval_frame.pack(padx=10, fill=tk.X)# 最大间隔时间max_interval_frame = ttk.Frame(control_frame)max_interval_label = ttk.Label(max_interval_frame, text="最大间隔时间(秒):")self.max_interval_entry = ttk.Entry(max_interval_frame, width=5)self.max_interval_entry.insert(0, str(self.max_interval)) # 默认值1秒max_interval_label.grid(row=0, column=0, padx=(10, 0), pady=(10, 0))self.max_interval_entry.grid(row=0, column=1, padx=(0, 10), pady=(10, 0))max_interval_frame.pack(padx=10, fill=tk.X)# 录制选项record_options_frame = ttk.LabelFrame(control_frame, text="录制选项")record_options_frame.pack(padx=10, pady=10, fill=tk.X)single_click_checkbox = ttk.Checkbutton(record_options_frame, text="单击", variable=self.record_single_click)double_click_checkbox = ttk.Checkbutton(record_options_frame, text="双击", variable=self.record_double_click)dragging_checkbox = ttk.Checkbutton(record_options_frame, text="拖动", variable=self.record_dragging)scroll_checkbox = ttk.Checkbutton(record_options_frame, text="滚动", variable=self.record_scroll)key_press_checkbox = ttk.Checkbutton(record_options_frame, text="按键", variable=self.record_key_press)mouse_move_checkbox = ttk.Checkbutton(record_options_frame, text="移动", variable=self.record_mouse_move)single_click_checkbox.grid(row=0, column=0, sticky=tk.W, padx=10, pady=5)double_click_checkbox.grid(row=1, column=0, sticky=tk.W, padx=10, pady=5)dragging_checkbox.grid(row=2, column=0, sticky=tk.W, padx=10, pady=5)scroll_checkbox.grid(row=0, column=1, sticky=tk.W, padx=10, pady=5)key_press_checkbox.grid(row=1, column=1, sticky=tk.W, padx=10, pady=5)mouse_move_checkbox.grid(row=2, column=1, sticky=tk.W, padx=10, pady=5)# 录制按钮record_button = ttk.Button(control_frame, text="开始/停止录制 (F7)", command=self.toggle_recording)record_button.pack(pady=(10, 0))# 清除录制按钮clear_button = ttk.Button(control_frame, text="清除录制 (F1)", command=self.clear_recorded_actions)clear_button.pack(pady=(10, 0))# 回放按钮play_button = ttk.Button(control_frame, text="开始回放 (F2)", command=self.start_playing)play_button.pack(pady=(10, 0))# 停止回放按钮self.stop_button = ttk.Button(control_frame, text="停止回放 (F3)", command=self.stop_playing, state=tk.DISABLED)self.stop_button.pack(pady=(10, 0))# 保存录制的动作到文件save_button = ttk.Button(control_frame, text="保存录制的动作 (F4)", command=self.save_recorded_actions)save_button.pack(pady=(10, 0))# 加载录制的动作从文件load_button = ttk.Button(control_frame, text="加载录制的动作 (F5)", command=self.load_recorded_actions)load_button.pack(pady=(10, 0))# 记录的动作列表self.action_listbox = tk.Listbox(main_frame, height=30, width=100)self.action_listbox.pack(pady=(10, 0), fill=tk.BOTH, expand=True)# 状态面板status_frame = ttk.Frame(main_frame)status_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=10, pady=10)self.status_text = tk.Text(status_frame, wrap=tk.WORD, height=10, width=100)self.status_text.pack(expand=True, fill=tk.BOTH, padx=10, pady=10)def toggle_recording(self):if self.recording_controller.recording:self.recording_controller.stop_recording()self.update_action_listbox()self.update_status_panel("录制已停止")else:self.recording_controller.start_recording()self.update_status_panel("正在录制... 按 F1 结束录制")def update_action_listbox(self, action=None):self.action_listbox.delete(0, tk.END)for action in self.action_manager.get_actions():formatted_action = self.format_action(action)self.action_listbox.insert(tk.END, formatted_action)def format_action(self, action):if action["type"] == "mouse_click":press_state = "按下" if action["pressed"] else "释放"return f"鼠标点击 ({action['position'][0]}, {action['position'][1]}), 按钮: {action['button']}, 状态: {press_state}"elif action["type"] == "mouse_scroll":return f"鼠标滚轮 ({action['position'][0]}, {action['position'][1]}), 变量: ({action['dx']}, {action['dy']})"elif action["type"] == "key_press":return f"按键: {action['key']}"elif action["type"] == "mouse_move":return f"鼠标移动到 ({action['position'][0]}, {action['position'][1]})"elif action["type"] == "mouse_press":return f"鼠标按住 ({action['position'][0]}, {action['position'][1]}), 按钮: {action['button']}"elif action["type"] == "mouse_release":return f"鼠标释放 ({action['position'][0]}, {action['position'][1]}), 按钮: {action['button']}"return ""def validate_interval(self, value_if_allowed):try:if float(value_if_allowed) <= 0 or float(value_if_allowed) > 10:raise ValueErrorreturn Trueexcept ValueError:return Falsedef start_playing(self):try:repeat_times = int(self.repeat_times_entry.get())delay = int(self.delay_entry.get())min_interval = float(self.min_interval_entry.get())max_interval = float(self.max_interval_entry.get())except ValueError:self.update_status_panel("请输入有效的数值。")returnif min_interval < 0 or min_interval > 60 or max_interval < 0 or max_interval > 60 or min_interval >= max_interval:messagebox.showwarning("无效的间隔时间", "请将最小和最大间隔时间设置在0.1秒到10秒之间,并且最小间隔时间应小于最大间隔时间。")returnself.controller.start_playing(repeat_times, min_interval, max_interval, delay)self.stop_button["state"] = tk.NORMALdef stop_playing(self):self.controller.stop_playing()self.stop_button["state"] = tk.DISABLEDdef save_recorded_actions(self):default_filename = "录制自动点击脚本.txt"file_path = filedialog.asksaveasfilename(defaultextension=".txt",initialfile=default_filename,filetypes=[("文本文件", "*.txt"),("所有文件", "*.*")])if file_path:self.action_manager.save_to_file(file_path)self.update_status_panel(f"动作已保存到 {file_path}")def load_recorded_actions(self):file_path = filedialog.askopenfilename(filetypes=[("文本文件", "*.txt"),("所有文件", "*.*")])if file_path:self.action_manager.load_from_file(file_path)self.update_action_listbox()self.update_status_panel(f"动作已从 {file_path} 加载")def cycle_clicks(self):if self.action_manager.get_actions():self.controller.start_playing(999, 0, 0, 0) # 循环点击,无间隔,无延迟self.update_status_panel("循环点击启动")def clear_recorded_actions(self):self.action_manager.clear_actions()self.update_action_listbox()self.update_status_panel("录制已清除")def update_status_panel(self, message):self.status_text.insert(tk.END, message + "\n")self.status_text.see(tk.END)if __name__ == "__main__":root = tk.Tk()root.title("动作录制与回放工具")tab = RecordingPlaybackTab(root)tab.pack(fill=tk.BOTH, expand=True)# 监听全局键盘事件global_keyboard_listener = KeyboardListener(on_press=lambda key: tab.recording_controller.on_press(key))global_keyboard_listener.start()root.mainloop()