import os
import json
import time
import threading
import datetime
import pyautogui
import sys
from tkinter import Tk, Frame, LabelFrame, Label, Entry, Button as TkButton, Listbox, Text, Scrollbar, StringVar, \
DISABLED, NORMAL, END, simpledialog, OptionMenu, Toplevel, messagebox, ttk, IntVar
from tkinter.font import Font
from pynput.mouse import Listener as MouseListener, Button
from pynput.keyboard import Controller
class ActionRecorder:
def __init__(self):
self.recording = False
self.playing = False
self.actions = []
self.current_config = {
"script_name": "未命名脚本",
"speed_multiplier": 1,
"loop_count": 1
}
self.configs = {}
self.keyboard = Controller()
self.current_script_name = None
# 加载配置文件
self.load_configs()
if not os.path.exists("logs"):
os.maked极os.makedirs("logs", exist_ok=True)
self.init_gui()
self.setup_hotkeys()
self.base_time = None
self.mouse_state = {"left": False, "right": False, "middle": False}
def setup_hotkeys(self):
"""设置全局快捷键 - 只保留鼠标操作"""
# 不再监听键盘快捷键
pass
def init_gui(self):
"""初始化GUI界面"""
self.root = Tk()
self.root.title("动作录制器 v10.0")
self.root.geometry("800x700")
# 设置默认字体
bold_font = Font(family="微软雅黑", size=10, weight="bold")
normal_font = Font(family="微软雅黑", size=9)
# 配置区域
config_frame = LabelFrame(self.root, text="配置", padx=10, pady=10, font=bold_font)
config_frame.pack(fill="x", padx=10, pady=5)
Label(config_frame, text="脚本名称:", font=normal_font).grid(row=0, column=0, sticky="e", pady=5)
self.script_name_entry = Entry(config_frame, font=normal_font)
self.script_name_entry.grid(row=0, column=1, sticky="we", padx=5, pady=5)
self.script_name_entry.insert(0, self.current_config["script_name"])
# 循环次数配置
Label(config_frame, text="循环次数:", font=normal_font).grid(row=1, column=0, sticky="e", pady=5)
self.loop_var = IntVar(value=self.current_config["loop_count"])
loop_entry = Entry(config_frame, textvariable=self.loop_var, font=normal_font)
loop_entry.grid(row=1, column=1, sticky="we", padx=5, pady=5)
Label(config_frame, text="播放速度:", font=normal_font).grid(row=2, column=0, sticky="e", pady=5)
speeds = ["0.5倍速", "1倍速", "2倍速", "3倍速", "4倍速", "5倍速", "6倍速", "7倍速", "8倍极", "9倍速", "10倍速"]
self.speed_var = StringVar(value=speeds[1]) # 默认选中1倍速
self.speed_menu = OptionMenu(config_frame, self.speed_var, *speeds, command=self.update_speed_multiplier)
self.speed_menu.grid(row=2, column=1, sticky="we", padx=5, pady=5)
# 按钮区域
button_frame = Frame(self.root)
button_frame.pack(fill="x", padx=10, pady=10)
self.record_btn = TkButton(button_frame, text="开始/停止录制 (F2)", command=self.toggle_recording, bg="#4CAF50",
fg="white")
self.record_btn.pack(side="left", padx=5, ipadx=10, ipady=5)
self.play_btn = TkButton(button_frame, text="开始/停止执行 (F12)", command=self.toggle_playing, bg="#2196F3",
fg="white")
self.play_btn.pack(side="left", padx=5, ipadx=10, ipady=5)
self.edit_btn = TkButton(button_frame, text="编辑脚本", command=self.edit_script, bg="#FFC107", fg="white")
self.edit_btn.pack(side="left", padx=5, ipadx=10, ipady=5)
self.save_btn = TkButton(button_frame, text="保存脚本", command=self.save_script, bg="#9C27B0", fg="white")
self.save_btn.pack(side="right", padx=5, ipadx=10, ipady=5)
self.delete_btn = TkButton(button_frame, text="删除脚本", command=self.delete_script, bg="#607D8B", fg="white")
self.delete_btn.pack(side="right", padx=5, ipadx=10, ipady=5)
self.rename_btn = TkButton(button_frame, text="重命名脚本", command=self.rename_script, bg="#FF9800", fg="white")
self.rename_btn.pack(side="right", padx=5, ipadx=10, ipady=5)
self.clear_log_btn = TkButton(button_frame, text="清除日志", command=self.clear_log, bg="#E91E63", fg="white")
self.clear_log_btn.pack(side="right", padx=5, ipadx=10, ipady=5)
# 主内容区域
main_frame = Frame(self.root)
main_frame.pack(fill="both", expand=True, padx=10, pady=5)
# 脚本列表
script_frame = LabelFrame(main_frame, text="保存的脚本", padx=10, pady=10, font=bold_font)
script_frame.pack(side="left", fill="y", padx=5, pady=5)
self.script_listbox = Listbox(script_frame, font=normal_font, width=25, height=15)
self.script_listbox.pack(fill="both", expand=True, padx=5, pady=5)
self.script_listbox.bind("<Double-Button-1>", self.load_script)
# 日志区域
log_frame = LabelFrame(main_frame, text="操作日志", padx=10, pady=10, font=bold_font)
log_frame.pack(side="right", fill="both", expand=True, padx=5, pady=5)
self.log_text = Text(log_frame, font=normal_font, wrap="word")
scrollbar = Scrollbar(log_frame, command=self.log_text.yview)
self.log_text.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side="right", fill="y")
self.log_text.pack(fill="both", expand=True, padx=5, pady=5)
# 状态栏
self.status_var = StringVar(value="就绪")
status_bar = Label(self.root, textvariable=self.status_var, bd=1, relief="sunken", anchor="w", font=normal_font)
status_bar.pack(fill="x", padx=10, pady=5)
self.update_script_list()
def load_configs(self):
"""加载配置文件"""
try:
if os.path.exists("configs.json"):
with open("configs.json", "r", encoding="utf-8") as f:
self.configs = json.load(f)
except Exception as e:
self.log_message(f"加载配置失败: {str(e)}")
self.configs = {}
def save_configs(self):
"""保存配置文件"""
try:
with open("configs.json", "w", encoding="utf-8") as f:
json.dump(self.configs, f, ensure_ascii=False, indent=2)
except Exception as e:
self.log_message(f"保存配置失败: {str(e)}")
def toggle_recording(self):
"""切换录制状态 - 只录制鼠标"""
if self.recording:
self.stop_recording()
else:
threading.Thread(target=self.start_recording, daemon=True).start()
def start_recording(self):
"""开始录制 - 只录制鼠标"""
if self.recording or self.playing:
return
self.recording = True
self.actions = []
self.base_time = time.time()
self.mouse_state = {"left": False, "right": False, "middle": False} # 重置鼠标状态
# 更新按钮状态
self.root.after(0, lambda: self.record_btn.config(bg="#F44336", text="停止录制 (F2)"))
self.root.after(0, lambda: self.play_btn.config(state=DISABLED))
self.root.after(0, lambda: self.save_btn.config(state=DISABLED))
self.root.after(0, lambda: self.delete_btn.config(state=DISABLED))
self.root.after(0, lambda: self.rename_btn.config(state=DISABLED))
self.root.after(0, lambda: self.edit_btn.config(state=DISABLED))
# 启动鼠标监听器 - 不再监听键盘
self.mouse_listener = MouseListener(
on_click=self.on_mouse_click,
on_scroll=self.on_mouse_scroll,
on_move=self.on_mouse_move
)
self.mouse_listener.start()
self.log_message("开始录制鼠标操作...")
self.log_message("请开始您的鼠标操作,按F2停止录制")
self.log_message("注意:键盘操作需在编辑脚本时手动添加")
def stop_recording(self):
"""停止录制并提示保存"""
if not self.recording:
return
self.recording = False
# 停止监听器
if hasattr(self, 'mouse_listener') and self.mouse_listener:
self.mouse_listener.stop()
# 更新按钮状态
self.root.after(0, lambda: self.record_btn.config(bg="#4CAF50", text="开始录制 (F2)"))
self.root.after(0, lambda: self.play_btn.config(state=NORMAL))
self.root.after(0, lambda: self.save_btn.config(state=NORMAL))
self.root.after(0, lambda: self.delete_btn.config(state=NORMAL))
self.root.after(0, lambda: self.rename_btn.config(state=NORMAL))
self.root.after(0, lambda: self.edit_btn.config(state=NORMAL))
self.log_message(f"停止录制,共录制了 {len(self.actions)} 个鼠标动作")
# 录制完成后提示是否保存
if self.actions:
self.root.after(100, self.prompt_save_script)
def prompt_save_script(self):
"""提示用户保存脚本"""
if not self.actions:
return
script_name = self.script_name_entry.get().strip()
if not script_name:
script_name = "未命名脚本"
response = messagebox.askyesno("保存脚本",
f"录制完成,共录制了 {len(self.actions)} 个鼠标动作\n是否保存脚本 '{script_name}'?",
parent=self.root)
if response:
self.save_script()
def on_mouse_click(self, x, y, button, pressed):
"""鼠标点击事件"""
if not self.recording:
return
button_name = str(button).split('.')[-1]
action_type = "mousedown" if pressed else "mouseup"
self.actions.append({
"type": action_type,
"button": button_name,
"x": x,
"y": y,
"time": time.time() - self.base_time
})
self.log_message(f"{'按下' if pressed else '释放'} 鼠标 {button_name} 键 位置: ({x}, {y})")
# 更新鼠标状态
self.mouse_state[button_name] = pressed
def on_mouse_scroll(self, x, y, dx, dy):
"""鼠标滚轮事件"""
if not self.recording:
return
self.actions.append({
"type": "wheel",
"delta": dy,
"x": x,
"y": y,
"time": time.time() - self.base_time
})
self.log_message(f"滚轮滚动: {'上' if dy > 0 else '下'} 位置: ({x}, {y})")
def on_mouse_move(self, x, y):
"""鼠标移动事件"""
if not self.recording:
return
for btn, pressed in self.mouse_state.items():
if pressed:
self.actions.append({
"type": "mousemove",
"x": x,
"y": y,
"button": btn,
"time": time.time() - self.base_time
})
self.log_message(f"鼠标移动: 按住 {btn} 移动到 ({x}, {y})")
def update_speed_multiplier(self, value):
"""更新倍速乘数"""
speed_map = {"0.5倍速": 0.5, "1倍速": 1, "2倍速": 2, "3倍速": 3, "4倍速": 4,
"5倍速": 5, "6倍速": 6, "7倍速": 7, "8倍速": 8, "9倍速": 9, "10倍速": 10}
self.current_config["speed_multiplier"] = speed_map[value]
self.log_message(f"播放速度设置为: {value}")
def save_script(self):
"""保存脚本"""
if not self.actions:
messagebox.showwarning("警告", "没有录制的动作可以保存")
return
script_name = self.script_name_entry.get().strip()
if not script_name:
script_name = "未命名脚本"
# 更新循环次数配置
self.current_config["loop_count"] = self.loop_var.get()
self.current_config["script_name"] = script_name
self.current_script_name = script_name
self.configs[script_name] = {
"config": self.current_config,
"actions": self.actions
}
self.save_configs()
self.update_script_list()
self.log_message(f"脚本 '{script_name}' 已保存")
# 更新脚本名称输入框
self.script_name_entry.delete(0, END)
self.script_name_entry.insert(0, script_name)
def delete_script(self):
"""删除脚本"""
selection = self.script_listbox.curselection()
if not selection:
messagebox.showwarning("警告", "请先选择一个脚本")
return
script_name = self.script_listbox.get(selection[0])
if messagebox.askyesno("确认", f"确定要删除脚本 '{script_name}' 吗?"):
if script_name in self.configs:
del self.configs[script_name]
self.save_configs()
self.update_script_list()
self.log_message(f"脚本 '{script_name}' 已删除")
# 如果删除的是当前加载的脚本,清空当前脚本
if self.current_script_name == script_name:
self.current_script_name = None
self.actions = []
def rename_script(self):
"""重命名脚本"""
selection = self.script_listbox.curselection()
if not selection:
messagebox.showwarning("警告", "请先选择一个脚本")
return
old_script_name = self.script_listbox.get(selection[0])
new_script_name = simpledialog.askstring("重命名脚本", "请输入新的极名称",
initialvalue=old_script_name, parent=self.root)
if new_script_name and old_script_name in self.configs:
self.configs[new_script_name] = self.configs.pop(old_script_name)
self.save_configs()
self.update_script_list()
self.log_message(f"脚本 '{old_script_name}' 已重命名为 '{new_script_name}'")
self.current_script_name = new_script_name
# 更新脚本名称输入框
self.script_name_entry.delete(0, END)
self.script_name_entry.insert(0, new_script_name)
def update_script_list(self):
"""更新脚本列表"""
self.script_listbox.delete(0, END)
for script_name in sorted(self.configs.keys()):
self.script_listbox.insert(END, script_name)
def load_script(self, event=None):
"""加载脚本"""
selection = self.script_listbox.curselection()
if not selection:
return
script_name = self.script_listbox.get(selection[0])
if script_name in self.configs:
script_data = self.configs[script_name]
self.current_config = script_data["config"]
self.actions = script_data["actions"]
self.current_script_name = script_name
self.script_name_entry.delete(0, END)
self.script_name_entry.insert(0, self.current_config["script_name"])
# 设置循环次数
self.loop_var.set(self.current_config.get("loop_count", 1))
# 设置速度选项
speed_multiplier = self.current_config["speed_multiplier"]
speed_index = int(speed_multiplier * 2 - 1) # 0.5->0, 1->1, 2->3, etc.
speeds = ["0.5倍速", "1倍速", "2倍速", "3倍速", "4倍速", "5倍速", "6倍速", "7倍速", "8倍速", "9倍速", "10倍速"]
# 确保索引在有效范围内
if speed_index < 0:
speed_index = 0
elif speed_index >= len(speeds):
speed_index = len(speeds) - 1
self.speed_var.set(speeds[speed_index])
self.log_message(f"已加载脚本 '{script_name}'")
self.log_message(f"共 {len(self.actions)} 个动作")
def toggle_playing(self):
"""切换播放状态"""
if self.playing:
self.stop_playing()
else:
threading.Thread(target=self.start_playing, daemon=True).start()
def start_playing(self):
"""开始播放"""
if self.playing or self.recording or not self.actions:
return
self.playing = True
self.root.after(0, lambda: self.play_btn.config(bg="#FF9800", text="停止执行 (F12)"))
self.root.after(0, lambda: self.record_btn.config(state=DISABLED))
self.root.after(0, lambda: self.save_btn.config(state=DISABLED))
self.root.after(0, lambda: self.delete_btn.config(state=DISABLED))
self.root.after(0, lambda: self.rename_btn.config(state=DISABLED))
self.root.after(0, lambda: self.edit_btn.config(state=DISABLED))
# 获取循环次数
loop_count = self.loop_var.get()
if loop_count < 1:
loop_count = 1
threading.Thread(target=self.play_actions, args=(loop_count,), daemon=True).start()
self.log_message(f"开始执行脚本,循环次数: {loop_count}")
def stop_playing(self):
"""停止播放"""
if not self.playing:
return
self.playing = False
self.root.after(0, lambda: self.play_btn.config(bg="#2196F3", text="开始执行 (F12)"))
self.root.after(0, lambda: self.record_btn.config(state=NORMAL))
self.root.after(0, lambda: self.save_btn.config(state=NORMAL))
self.root.after(0, lambda: self.delete_btn.config(state=NORMAL))
self.root.after(0, lambda: self.rename_btn.config(state=NORMAL))
self.root.after(0, lambda: self.edit_btn.config(state=NORMAL))
self.log_message("停止执行脚本")
def play_actions(self, loop_count=1):
"""播放录制的动作 - 支持循环播放"""
speed_multiplier = self.current_config["speed_multiplier"]
for loop in range(loop_count):
if not self.playing:
break
self.log_message(f"开始第 {loop + 1}/{loop_count} 次循环")
prev_time = 0
pressed_keys = set() # 跟踪当前按下的键
for i, action in enumerate(self.actions):
if not self.playing:
break
# 计算延迟并等待
delay = (action["time"] - prev_time) / speed_multiplier
time.sleep(max(0, delay))
prev_time = action["time"]
try:
# 处理鼠标事件
if action["type"] == "mousedown":
pyautogui.mouseDown(x=action["x"], y=action["y"], button=action["button"])
self.log_message(
f"执行动作 {i + 1}/{len(self.actions)}: 在({action['x']}, {action['y']})按下 {action['button']}键")
elif action["type"] == "mouseup":
pyautogui.mouseUp(x=action["x"], y=action["y"], button=action["button"])
self.log_message(
f"执行动作 {i + 1}/{len(self.actions)}: 在({action['x']}, {action['y']})释放 {action['button']}键")
elif action["type"] == "wheel":
# 确保delta是整数
delta = int(action["delta"])
pyautogui.scroll(delta)
self.log_message(f"执行动作 {i + 1}/{len(self.actions)}: 滚轮滚动 {'上' if delta > 0 else '下'}")
elif action["type"] == "mousemove":
# 确保duration至少为0.001
duration = max(0.001, 0.1 / speed_multiplier)
pyautogui.moveTo(action["x"], action["y"], duration=duration)
self.log_message(f"执行动作 {i + 1}/{len(self.actions)}: 移动到 ({action['x']}, {action['y']})")
# 处理键盘事件
elif action["type"] == "keydown":
# 记录按下的键
pressed_keys.add(action["key"])
try:
pyautogui.keyDown(action["key"])
self.log_message(f"执行动作 {i + 1}/{len(self.actions)}: 按下 {action['key']}")
except Exception as e:
self.log_message(f"按键按下失败: {action['key']}, 错误: {str(e)}")
elif action["type"] == "keyup":
# 检查是否已按下
if action["key"] in pressed_keys:
pressed_keys.remove(action["key"])
try:
pyautogui.keyUp(action["key"])
self.log_message(f"执行动作 {i + 1}/{len(self.actions)}: 释放 {action['key']}")
except Exception as e:
self.log_message(f"按键释放失败: {action['key']}, 错误: {str(e)}")
except Exception as e:
self.log_message(f"执行动作时出错: {str(e)}")
# 释放所有未释放的按键
for key in list(pressed_keys):
try:
pyautogui.keyUp(key)
self.log_message(f"释放未完成的按键: {key}")
except Exception as e:
self.log_message(f"释放按键时出错: {str(e)}")
# 等待循环间隔
if loop < loop_count - 1 and self.playing:
time.sleep(0.5) # 循环间暂停0.5秒
self.stop_playing()
def log_message(self, message):
"""记录日志"""
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"[{timestamp}] {message}\n"
self.log_text.insert(END, log_entry)
self.log_text.see(END)
self.status_var.set(message)
today = datetime.datetime.now().strftime("%Y-%m-%d")
log_file = f"logs/{today}.log"
try:
with open(log_file, "a", encoding="utf-8") as f:
f.write(log_entry)
except Exception as e:
print(f"写入日志失败: {str(e)}")
def clear_log(self):
"""清除日志"""
self.log_text.delete(1.0, END)
self.log_message("日志已清除")
def edit_script(self):
"""编辑当前加载的脚本"""
if not self.actions and not self.current_script_name:
messagebox.showwarning("警告", "没有可编辑的脚本,请先录制或加载一个脚本")
return
# 创建编辑窗口
edit_window = Toplevel(self.root)
edit_window.title("编辑脚本")
edit_window.geometry("900x600")
edit_window.grab_set() # 使窗口模态
self.edit_window = edit_window # 保存引用
# 创建框架
main_frame = Frame(edit_window)
main_frame.pack(fill="both", expand=True, padx=10, pady=10)
# 动作列表框架
list_frame = Frame(main_frame)
list_frame.pack(fill="both", expand=True, pady=(0, 10))
# 创建Treeview来显示动作
columns = ("id", "type", "details", "time")
tree = ttk.Treeview(list_frame, columns=columns, show="headings", selectmode="browse")
# 设置列标题
tree.heading("id", text="序号")
tree.heading("type", text="动作类型")
tree.heading("details", text="详细信息")
tree.heading("time", text="时间(秒)")
# 设置列宽
tree.column("id", width=50, anchor="center")
tree.column("type", width=100, anchor="center")
tree.column("details", width=500, anchor="w")
tree.column("time", width=100, anchor="center")
# 添加滚动条
scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=tree.yview)
tree.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side="right", fill="y")
tree.pack(side="left", fill="both", expand=True)
# 填充数据
for i, action in enumerate(self.actions):
action_type = action["type"]
details = ""
if action_type in ["mousedown", "mouseup"]:
details = f"按钮: {action['button']}, 位置: ({action['x']}, {action['y']})"
elif action_type == "wheel":
details = f"滚动方向: {'上' if action['delta'] > 0 else '下'}, 位置: ({action['x']}, {action['y']})"
elif action_type == "mousemove":
details = f"按钮: {action['button']}, 位置: ({action['x']}, {action['y']})"
elif action_type in ["keydown", "keyup"]:
details = f"按键: {action['key']}"
tree.insert("", "end", values=(
i + 1,
self.get_action_name(action_type),
details,
f"{action['time']:.3f}"
))
# 按钮框架
btn_frame = Frame(main_frame)
btn_frame.pack(fill="x", pady=10)
# 鼠标动作按钮
mouse_btn_frame = Frame(btn_frame)
mouse_btn_frame.pack(side="left", fill="x", expand=True)
# 键盘动作按钮 - 新增
key_btn_frame = Frame(btn_frame)
key_btn_frame.pack(side="left", fill="x", expand=True, padx=20)
# 鼠标动作按钮
edit_btn = TkButton(mouse_btn_frame, text="编辑动作",
command=lambda: self.edit_action(tree, edit_window),
bg="#2196F3", fg="white")
edit_btn.pack(side="left", padx=5, ipadx=10, ipady=5)
delete_btn = TkButton(mouse_btn_frame, text="删除动作",
command=lambda: self.delete_action(tree),
bg="#F44336", fg="white")
delete_btn.pack(side="left", padx=5, ipadx=10, ipady=5)
move_up_btn = TkButton(mouse_btn_frame, text="上移",
command=lambda: self.move_action(tree, -1),
bg="#4CAF50", fg="white")
move_up_btn.pack(side="left", padx=5, ipadx=10, ipady=5)
move_down_btn = TkButton(mouse_btn_frame, text="下移",
command=lambda: self.move_action(tree, 1),
bg="#FF9800", fg="white")
move_down_btn.pack(side="left", padx=5, ipadx=10, ipady=5)
# 键盘动作按钮 - 新增
keydown_btn = TkButton(key_btn_frame, text="添加按键按下",
command=lambda: self.add_key_action(tree, "keydown"),
bg="#9C27B0", fg="white")
keydown_btn.pack(side="left", padx=5, ipadx=10, ipady=5)
keyup_btn = TkButton(key_btn_frame, text="添加按键释放",
command=lambda: self.add_key_action(tree, "keyup"),
bg="#673AB7", fg="white")
keyup_btn.pack(side="left", padx=5, ipadx=10, ipady=5)
keypress_btn = TkButton(key_btn_frame, text="添加按键组合",
command=lambda: self.add_key_combination(tree),
bg="#3F51B5", fg="white")
keypress_btn.pack(side="left", padx=5, ipadx=10, ipady=5)
# 保存按钮
save_btn_frame = Frame(btn_frame)
save_btn_frame.pack(side="right")
save_btn = TkButton(save_btn_frame, text="保存修改",
command=lambda: self.save_edit(tree, edit_window),
bg="#4CAF50", fg="white")
save_btn.pack(side="right", padx=5, ipadx=10, ipady=5)
# 保存对tree的引用
edit_window.tree = tree
def get_action_name(self, action_type):
"""获取动作类型的中文名称"""
names = {
"mousedown": "鼠标按下",
"mouseup": "鼠标释放",
"wheel": "滚轮滚动",
"mousemove": "鼠标移动",
"keydown": "按键按下",
"keyup": "按键释放"
}
return names.get(action_type, action_type)
def edit_action(self, tree, edit_window):
"""编辑选中的动作"""
selected = tree.selection()
if not selected:
messagebox.showwarning("警告", "请先选择一个动作")
return
# 获取选中的动作索引
index = int(tree.index(selected[0]))
action = self.actions[index]
# 创建编辑对话框
edit_dialog = Toplevel(edit_window)
edit_dialog.title("编辑动作")
edit_dialog.geometry("400x300")
edit_dialog.grab_set()
edit_dialog.transient(edit_window)
# 动作类型
type_frame = Frame(edit_dialog)
type_frame.pack(fill="x", padx=10, pady=10)
Label(type_frame, text="动作类型:").pack(side="left")
action_types = ["鼠标按下", "鼠标释放", "滚轮滚动", "鼠标移动", "按键按下", "按键释放"]
type_var = StringVar(value=self.get_action_name(action["type"]))
type_menu = OptionMenu(type_frame, type_var, *action_types)
type_menu.pack(side="left", padx=5)
# 详细信息框架
details_frame = Frame(edit_dialog)
details_frame.pack(fill="both", expand=True, padx=10, pady=10)
# 根据动作类型显示不同的编辑字段
if action["type"] in ["mousedown", "mouseup", "mousemove"]:
# 鼠标位置编辑
Label(details_frame, text="X坐标:").grid(row=0, column=0, sticky="e", pady=5)
x_var = StringVar(value=action["x"])
Entry(details_frame, textvariable=x_var).grid(row=0, column=1, sticky="we", padx=5, pady=5)
Label(details_frame, text="Y坐标:").grid(row=1, column=0, sticky="e", pady=5)
y_var = StringVar(value=action["y"])
Entry(details_frame, textvariable=y_var).grid(row=1, column=1, sticky="we", padx=5, pady=5)
# 鼠标按钮
Label(details_frame, text="鼠标按钮:").grid(row=2, column=0, sticky="e", pady=5)
button_var = StringVar(value=action.get("button", "left"))
buttons = ["left", "right", "middle"]
OptionMenu(details_frame, button_var, *buttons).grid(row=2, column=1, sticky="we", padx=5, pady=5)
elif action["type"] == "wheel":
# 滚轮滚动
Label(details_frame, text="滚动方向:").grid(row=0, column=0, sticky="e", pady=5)
direction_var = StringVar(value="上" if action["delta"] > 0 else "下")
OptionMenu(details_frame, direction_var, "上", "下").grid(row=0, column=1, sticky="we", padx=5, pady=5)
Label(details_frame, text="滚动量:").grid(row=1, column=0, sticky="e", pady=5)
delta_var = StringVar(value=abs(int(action["delta"])))
Entry(details_frame, textvariable=delta_var).grid(row=1, column=1, sticky="we", padx=5, pady=5)
Label(details_frame, text="X坐标:").grid(row=2, column=0, sticky="e", pady=5)
x_var = StringVar(value=action["x"])
Entry(details_frame, textvariable=x_var).grid(row=2, column=1, sticky="we", padx=5, pady=5)
Label(details_frame, text="Y坐标:").grid(row=3, column=0, sticky="e", pady=5)
y_var = StringVar(value=action["y"])
Entry(details_frame, textvariable=y_var).grid(row=3, column=1, sticky="we", padx=5, pady=5)
else: # keydown or keyup
# 按键编辑
Label(details_frame, text="按键:").grid(row=0, column=0, sticky="e", pady=5)
key_var = StringVar(value=action["key"])
Entry(details_frame, textvariable=key_var).grid(row=0, column=1, sticky="we", padx=5, pady=5)
# 时间编辑
time_frame = Frame(edit_dialog)
time_frame.pack(fill="x", padx=10, pady=10)
Label(time_frame, text="时间(秒):").pack(side="left")
time_var = StringVar(value=f"{action['time']:.3f}")
Entry(time_frame, textvariable=time_var).pack(side="left", padx=5)
# 保存按钮
def save_action():
try:
# 更新动作类型
type_mapping = {
"鼠标按下": "mousedown",
"鼠标释放": "mouseup",
"滚轮滚动": "wheel",
"鼠标移动": "mousemove",
"按键按下": "keydown",
"按键释放": "keyup"
}
new_type = type_mapping[type_var.get()]
# 根据动作类型更新参数
if new_type in ["mousedown", "mouseup", "mousemove"]:
action["x"] = int(x_var.get())
action["y"] = int(y_var.get())
action["button"] = button_var.get()
elif new_type == "wheel":
action["delta"] = float(delta_var.get()) * (1 if direction_var.get() == "上" else -1)
action["x"] = int(x_var.get())
action["y"] = int(y_var.get())
else: # keydown or keyup
action["key"] = key_var.get()
# 更新时间
action["time"] = float(time_var.get())
action["type"] = new_type
# 刷新树视图
self.refresh_tree(tree)
edit_dialog.destroy()
self.log_message(f"动作 {index + 1} 已更新")
except Exception as e:
messagebox.showerror("错误", f"更新动作时出错: {str(e)}")
save_btn = TkButton(edit_dialog, text="保存", command=save_action, bg="#4CAF50", f极="white")
save_btn.pack(side="right", padx=10, pady=10)
def add_key_action(self, tree, action_type):
"""添加键盘按键动作"""
# 获取当前选中的动作索引
selected = tree.selection()
index = len(self.actions) # 默认插入到最后
if selected:
index = int(tree.index(selected[0])) + 1
# 获取时间 - 使用选中动作的时间或最后一个动作的时间
action_time = 0.0
if self.actions:
if selected:
selected_index = min(int(tree.index(selected[0])), len(self.actions) - 1)
action_time = self.actions[selected_index]["time"]
else:
action_time = self.actions[-1]["time"]
# 弹出对话框获取按键名称
key_name = simpledialog.askstring("添加按键动作", f"请输入按键名称 ({'按下' if action_type == 'keydown' else '释放'}):",
parent=tree.winfo_toplevel())
if not key_name:
return
# 创建新的键盘动作
new_action = {
"type": action_type,
"key": key_name,
"time": action_time
}
# 插入到动作列表
self.actions.insert(index, new_action)
# 刷新树视图
self.refresh_tree(tree)
# 选择新添加的动作 - 修复索引问题
children = tree.get_children()
if children:
# 确保索引在有效范围内
select_index = min(index, len(children) - 1)
tree.selection_set(children[select_index])
tree.focus(children[select_index])
self.log_message(f"已添加按键{action_type}动作: {key_name}")
def add_key_combination(self, tree):
"""添加按键组合(按下+释放)"""
# 获取按键名称
key_name = simpledialog.askstring("添加按键组合", "请输入按键名称:",
parent=self.root)
if not key_name:
return
# 获取当前选中的动作索引
selected = tree.selection()
insert_index = len(self.actions) # 默认插入到最后
if selected:
insert_index = int(tree.index(selected[0])) + 1
# 获取时间 - 使用选中动作的时间或最后一个动作的时间
action_time = 0.0
if self.actions:
if selected:
selected_index = min(int(tree.index(selected[0])), len(self.actions) - 1)
action_time = self.actions[selected_index]["time"]
else:
action_time = self.actions[-1]["time"]
# 创建按键按下动作
keydown_action = {
"type": "keydown",
"key": key_name,
"time": action_time
}
# 创建按键释放动作
keyup_action = {
"type": "keyup",
"key": key_name,
"time": action_time + 0.1 # 0.1秒后释放
}
# 插入到动作列表
self.actions.insert(insert_index, keydown_action)
self.actions.insert(insert_index + 1, keyup_action)
# 刷新树视图
self.refresh_tree(tree)
# 选择新添加的释放动作
children = tree.get_children()
if children:
select_index = min(insert_index + 1, len(children) - 1)
tree.selection_set(children[select_index])
tree.focus(children[select_index])
self.log_message(f"已添加按键组合: {key_name}")
def delete_action(self, tree):
"""删除选中的动作"""
selected = tree.selection()
if not selected:
messagebox.showwarning("警告", "请先选择一个动作")
return
# 获取选中的动作索引
index = int(tree.index(selected[0]))
if messagebox.askyesno("确认", f"确定要删除动作 {index + 1} 吗?"):
del self.actions[index]
self.refresh_tree(tree)
self.log_message(f"动作 {index + 1} 已删除")
def move_action(self, tree, direction):
"""上移或下移动作"""
selected = tree.selection()
if not selected:
messagebox.showwarning("警告", "请先选择一个动作")
return
# 获取选中的动作索引
index = int(tree.index(selected[0]))
# 检查边界
if (direction == -1 and index == 0) or (direction == 1 and index == len(self.actions) - 1):
return
# 交换动作位置
new_index = index + direction
self.actions[index], self.actions[new_index] = self.actions[new_index], self.actions[index]
# 刷新树视图
self.refresh_tree(tree)
# 重新选择移动后的动作
children = tree.get_children()
if children:
tree.selection_set(children[new_index])
tree.focus(children[new_index])
self.log_message(f"动作 {index + 1} 已{'上移' if direction == -1 else '下移'}")
def refresh_tree(self, tree):
"""刷新树视图以显示更新后的动作"""
# 保存当前选择
selected = tree.selection()
# 清除树视图
for item in tree.get_children():
tree.delete(item)
# 重新填充数据
for i, action in enumerate(self.actions):
action_type = action["type"]
details = ""
if action_type in ["mousedown", "mouseup"]:
details = f"按钮: {action['button']}, 位置: ({action['x']}, {action['y']})"
elif action_type == "wheel":
details = f"滚动方向: {'上' if action['delta'] > 0 else '下'}, 位置: ({action['x']}, {action['y']})"
elif action_type == "mousemove":
details = f"按钮: {action['button']}, 位置: ({action['x']}, {action['y']})"
elif action_type in ["keydown", "keyup"]:
details = f"按键: {action['key']}"
tree.insert("", "end", values=(
i + 1,
self.get_action_name(action_type),
details,
f"{action['time']:.3f}"
))
# 恢复选择
if selected:
tree.selection_set(selected)
tree.focus(selected)
def save_edit(self, tree, edit_window):
"""保存编辑后的脚本"""
if not self.current_script_name:
# 如果脚本尚未保存,提示用户先保存
if messagebox.askyesno("保存脚本", "当前脚本尚未保存,是否保存?", parent=edit_window):
self.save_script()
edit_window.destroy()
return
# 更新配置中的循环次数
self.current_config["loop_count"] = self.loop_var.get()
self.configs[self.current_script_name]["actions"] = self.actions
self.configs[self.current_script_name]["config"] = self.current_config
self.save_configs()
self.log_message(f"脚本 '{self.current_script_name}' 已更新")
edit_window.destroy()
# 测试代码
if __name__ == "__main__":
recorder = ActionRecorder()
recorder.root.mainloop()
帮我优化一下这些代码,1、修复编辑脚本时候的报错。2、修复快捷键无法开始录制和执行脚本的错误,3、在编辑脚本时,做一个快捷添加常用快捷键功能。
最新发布