关于ENTER_FRAME的执行顺序问题

本文探讨了在ActionScript中ENTER_FRAME事件的执行顺序,通过一个实例展示了事件监听器如何按照代码添加的顺序而非父级与子级关系执行。在代码示例中,即使子级精灵添加了ENTER_FRAME监听器,它们的执行顺序仍然遵循先根节点后子节点的顺序,揭示了ENTER_FRAME事件处理的有趣特性。

今天我在优快云中看到一个有关“关于ENTER_FRAME的执行顺序问题”我觉得很有意思,我试着编写了一段代码:

var ar:Array=new Array()
var sp:Sprite=new Sprite()
sp.name="SP"
addChild(sp)

addEventListener(Event.ENTER_FRAME,xfu)
function xfu(e:Event)
{
 trace(e.target.name)
}
for (var i:int=0;i<4;i++)
{
 ar[i]=new Sprite()
 ar[i].name="Sp_"+i
 sp.addChild(ar[i])
 ar[i].addEventListener(Event.ENTER_FRAME,xfu)
}
sp.addEventListener(Event.ENTER_FRAME,xfu)

则输出:

root1

Sp_0

Sp_1

Sp_2

Sp_3

SP

 

由此可见:执行顺序是按代码的先后执行,不讲父级和子级的。

 

import tkinter as tk from tkinter import font #from PIL import Image, ImageTk #import os from tkinter import messagebox from tools.pacong import req class ResponsiveApp: def __init__(self, root): self.root = root self.root.title("高级界面示例") # 设置窗口标题 self.base_width, self.base_height = 400, 300 self.root.geometry(f"{self.base_width}x{self.base_height}") self.base_font = font.Font(family="Arial", size=50) # 设置窗口图标(需要准备icon.ico文件) try: self.root.iconbitmap("icon.ico") # 设置窗口图标 except: print("图标文件未找到,使用默认图标") # 创建自定义字体对象(用于缩放) self.base_font = font.Font(family="Arial", size=10) # 主框架容器 self.main_frame = tk.Frame(root) self.main_frame.pack(fill=tk.BOTH, expand=True) # 布局界面元素 self.create_widgets() # 绑定窗口大小变化事件 self.root.bind("<Configure>", self.on_window_resize) def create_widgets(self): """创建界面元素""" # 创建文本框框架(用于居中) self.text_frame = tk.Frame(self.main_frame) self.text_frame.pack(expand=True, fill=tk.BOTH, pady=(10, 20)) # 创建文本框 self.text_box = tk.Text( self.text_frame, width=40, height=12, bg="#f0f0f0", font=self.base_font ) self.text_box.pack(expand=True, padx=20, pady=10) self.text_box.insert(tk.END, "https://") self.text_box.bind("<Control-Return>", on_enter) # 创建底部按钮框架 self.button_frame = tk.Frame(self.main_frame) self.button_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=10) # 创建确认按钮(带功能绑定) self.btn_ok = tk.Button( self.button_frame, text="确认", command=self.on_confirm, font=self.base_font ) self.btn_ok.pack(side=tk.LEFT, padx=20, pady=5, expand=True) # 创建取消按钮(带退出功能) self.btn_cancel = tk.Button( self.button_frame, text="取消", command=self.root.destroy, # 直接退出程序 font=self.base_font ) self.btn_cancel.pack(side=tk.LEFT, padx=20, pady=5, expand=True) # 设置窗口关闭协议(点击窗口关闭按钮时执行) self.root.protocol("WM_DELETE_WINDOW", self.root.destroy) def on_confirm(self): """确认按钮点击事件处理""" content = self.text_box.get("1.0", tk.END).strip() if content: print(f"用户输入内容: {content}") req(content) # 这里可以添加实际业务逻辑 tk.messagebox.showinfo("操作确认", "操作已执行!") else: tk.messagebox.showwarning("输入为空", "请输入文本内容") def on_window_resize(self, event): """窗口大小变化时调整控件和字体比例""" if event.widget != self.root: return # 计算当前窗口与基础尺寸的比例 width_ratio = event.width / self.base_width height_ratio = event.height / self.base_height scale_factor = min(width_ratio, height_ratio) * 0.85 # 取最小比例并稍微缩小 # 动态调整字体大小(保持比例) new_size = max(15, int(10 * scale_factor)) # 最小字体大小为8 self.base_font.configure(size=new_size) # 调整文本框尺寸(可选) new_width = max(20, int(40 * scale_factor)) new_height = max(5, int(12 * scale_factor)) self.text_box.config(width=new_width, height=new_height) def on_enter(event): c= text_box.get("1.0", tk.END) req(c) def main(): root = tk.Tk() app = ResponsiveApp(root) root.mainloop() if __name__ == "__main__": main() 上面代码提示AttributeError: 'ResponsiveApp' object has no attribute 'text_box'
08-29
修改文字识别方案为ddddocr:import tkinter as tk from tkinter import ttk, scrolledtext, messagebox, simpledialog import time import pyautogui import pyperclip import threading import random import queue import os import sys import subprocess import platform from PIL import ImageGrab, Image class SuperTyperPro: def __init__(self): self.root = tk.Tk() self.root.title("超级打字助手 Pro v4.2") self.root.geometry("750x700") self.root.resizable(True, True) # 全局状态控制 self.running_flag = False self.custom_content = [] self.default_content = [str(i) for i in range(10)] self.interval = 1.0 # 默认间隔1秒 # 模式系统 - 永久设置为最强模式 self.mode = "ultimate" self.max_content_items = 999 # 无限制 self.min_interval = 0.05 # 最快0.05秒 self.random_output_mode = False # 性能优化 self.log_queue = queue.Queue() self.last_log_time = 0 self.log_batch = [] # 文字识别相关 self.text_detection_enabled = False self.text_detection_interval = 5 # 5秒检测一次 self.text_to_detect = "生成" self.tesseract_installed = False self.tesseract_path = "" # 检查并设置 Tesseract OCR self.setup_tesseract() # 创建界面 self.setup_ui() # 启动日志处理循环 self.root.after(100, self.process_log_queue) # 设置关闭事件处理 self.root.protocol("WM_DELETE_WINDOW", self.on_closing) # 显示模式信息 self.update_mode_display() def setup_tesseract(self): """设置 Tesseract OCR 路径""" try: # 尝试导入 pytesseract import pytesseract self.tesseract_installed = True # 尝试自动查找 Tesseract 路径 self.tesseract_path = self.find_tesseract_path() if self.tesseract_path: pytesseract.pytesseract.tesseract_cmd = self.tesseract_path self.fast_log(f"✅ Tesseract 已配置: {self.tesseract_path}") else: self.tesseract_installed = False self.fast_log("⚠️ 未找到 Tesseract OCR,文字识别功能将不可用") except ImportError: self.tesseract_installed = False self.fast_log("⚠️ pytesseract 未安装,文字识别功能将不可用") def find_tesseract_path(self): """尝试在常见位置查找 Tesseract 可执行文件""" # Windows 常见安装路径 if platform.system() == "Windows": paths = [ r"C:\Program Files\Tesseract-OCR\tesseract.exe", r"C:\Program Files (x86)\Tesseract-OCR\tesseract.exe" ] for path in paths: if os.path.exists(path): return path # 对于 Linux/macOS,尝试在 PATH 中查找 try: result = subprocess.run(['which', 'tesseract'], capture_output=True, text=True) if result.returncode == 0 and result.stdout.strip(): return result.stdout.strip() except: pass return "" def install_tesseract(self): """提供安装 Tesseract OCR 的指导""" messagebox.showinfo( "安装 Tesseract OCR", "文字识别功能需要 Tesseract OCR 引擎:\n\n" "1. Windows 用户:\n" " - 下载安装程序: https://github.com/UB-Mannheim/tesseract/wiki\n" " - 安装时勾选 'Additional language data' 并选择中文\n" " - 安装后重启程序\n\n" "2. macOS 用户:\n" " - 使用 Homebrew: brew install tesseract\n" " - 安装中文语言包: brew install tesseract-lang\n\n" "3. Linux 用户 (Debian/Ubuntu):\n" " - sudo apt install tesseract-ocr\n" " - sudo apt install tesseract-ocr-chi-sim\n\n" "安装后请重启本程序。" ) def setup_ui(self): """创建用户界面""" # 设置样式 self.setup_styles() # 创建主框架 main_frame = ttk.Frame(self.root) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 上半部分:控制面板 control_frame = ttk.Frame(main_frame) control_frame.pack(fill=tk.BOTH, expand=True) # 标题和模式信息 title_frame = ttk.Frame(control_frame) title_frame.pack(fill=tk.X, pady=(0, 15)) # 应用标题 title_label = ttk.Label(title_frame, text="超级打字助手 Pro", style="Title.TLabel") title_label.pack(side=tk.LEFT) # 版本信息 version_label = ttk.Label(title_frame, text="v4.2", style="Version.TLabel") version_label.pack(side=tk.LEFT, padx=(5, 0)) # 右侧按钮区域 right_frame = ttk.Frame(title_frame) right_frame.pack(side=tk.RIGHT) self.mode_label = ttk.Label(right_frame, text="", style="Mode.TLabel") self.mode_label.pack(side=tk.TOP, anchor=tk.E) button_frame = ttk.Frame(right_frame) button_frame.pack(side=tk.TOP, pady=(5, 0)) about_btn = ttk.Button(button_frame, text="关于", command=self.show_about, width=8) about_btn.pack(side=tk.LEFT, padx=2) # 控制按钮框架 control_buttons_frame = ttk.Frame(control_frame) control_buttons_frame.pack(fill=tk.X, pady=5) # 使用tk.Button并设置正确的样式 self.start_btn = tk.Button( control_buttons_frame, text="开始输入 (F9)", command=self.toggle_start, bg="white", fg="black", font=("Microsoft YaHei", 9), relief="raised", bd=1, padx=10, pady=5, highlightbackground="black", highlightcolor="blue", highlightthickness=1 ) self.start_btn.pack(side=tk.LEFT, padx=5) self.stop_btn = tk.Button( control_buttons_frame, text="停止输入 (F10)", command=self.stop_typing, state=tk.DISABLED, bg="white", fg="black", font=("Microsoft YaHei", 9), relief="raised", bd=1, padx=10, pady=5, highlightbackground="black", highlightcolor="blue", highlightthickness=1 ) self.stop_btn.pack(side=tk.LEFT, padx=5) content_btn = tk.Button( control_buttons_frame, text="设置内容 (F11)", command=self.open_content_dialog, bg="white", fg="black", font=("Microsoft YaHei", 9), relief="raised", bd=1, padx=10, pady=5, highlightbackground="black", highlightcolor="blue", highlightthickness=1 ) content_btn.pack(side=tk.LEFT, padx=5) # 随机输出模式按钮 self.random_mode_btn = tk.Button( control_buttons_frame, text="随机模式: 关闭", command=self.toggle_random_mode, bg="#f0f0f0", fg="black", font=("Microsoft YaHei", 9), relief="raised", bd=1, padx=10, pady=5, highlightbackground="black", highlightcolor="blue", highlightthickness=1 ) self.random_mode_btn.pack(side=tk.LEFT, padx=5) # 文字识别按钮 self.text_detect_btn = tk.Button( control_buttons_frame, text="文字识别: 关闭", command=self.toggle_text_detection, bg="#f0f0f0", fg="black", font=("Microsoft YaHei", 9), relief="raised", bd=1, padx=10, pady=5, highlightbackground="black", highlightcolor="blue", highlightthickness=1 ) self.text_detect_btn.pack(side=tk.LEFT, padx=5) # OCR状态显示 self.ocr_status_var = tk.StringVar(value="OCR状态: 未知") ocr_status_label = ttk.Label(control_buttons_frame, textvariable=self.ocr_status_var, style="Small.TLabel") ocr_status_label.pack(side=tk.LEFT, padx=10) self.update_ocr_status() # 频率控制框架 freq_frame = ttk.LabelFrame(control_frame, text="发送频率控制", padding="15") freq_frame.pack(fill=tk.X, pady=10) # 频率滑块 freq_scale_frame = ttk.Frame(freq_frame) freq_scale_frame.pack(fill=tk.X, pady=5) ttk.Label(freq_scale_frame, text="快", style="Small.TLabel").pack(side=tk.LEFT) self.freq_scale = tk.Scale( freq_scale_frame, from_=self.min_interval, to=5.0, resolution=0.1, orient=tk.HORIZONTAL, length=350, showvalue=True, command=self.update_frequency, troughcolor="#e1e1e1", sliderrelief="raised", bg="#f5f5f5" ) self.freq_scale.set(self.interval) self.freq_scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=10) ttk.Label(freq_scale_frame, text="慢", style="Small.TLabel").pack(side=tk.LEFT) # 预设频率按钮 preset_frame = ttk.Frame(freq_frame) preset_frame.pack(fill=tk.X, pady=5) ttk.Button(preset_frame, text="1秒1条", command=lambda: self.set_preset_frequency(1.0), style="Small.TButton").pack(side=tk.LEFT, padx=2) ttk.Button(preset_frame, text="1秒2条", command=lambda: self.set_preset_frequency(0.5), style="Small.TButton").pack(side=tk.LEFT, padx=2) ttk.Button(preset_frame, text="1秒5条", command=lambda: self.set_preset_frequency(0.2), style="Small.TButton").pack(side=tk.LEFT, padx=2) ttk.Button(preset_frame, text="1秒10条", command=lambda: self.set_preset_frequency(0.1), style="Small.TButton").pack(side=tk.LEFT, padx=2) ttk.Button(preset_frame, text="1秒20条", command=lambda: self.set_preset_frequency(0.05), style="Small.TButton").pack(side=tk.LEFT, padx=2) # 当前频率显示 self.freq_var = tk.StringVar(value=f"当前频率: 每{self.interval}秒发送一条") freq_label = ttk.Label(freq_frame, textvariable=self.freq_var, style="Bold.TLabel") freq_label.pack(pady=5) # 状态显示 status_frame = ttk.LabelFrame(control_frame, text="状态信息", padding="10") status_frame.pack(fill=tk.X, pady=10) self.status_var = tk.StringVar(value="就绪 - 等待开始...") status_label = ttk.Label(status_frame, textvariable=self.status_var, style="Status.TLabel") status_label.pack(anchor=tk.W) # 当前内容显示 content_frame = ttk.LabelFrame(control_frame, text="当前输出内容", padding="10") content_frame.pack(fill=tk.BOTH, expand=True, pady=10) self.content_text = scrolledtext.ScrolledText( content_frame, height=6, wrap=tk.WORD, bg="#fafafa", relief="solid", borderwidth=1 ) self.content_text.pack(fill=tk.BOTH, expand=True) self.update_content_display() # 日志面板 log_frame = ttk.LabelFrame(control_frame, text="操作日志") log_frame.pack(fill=tk.BOTH, expand=True, pady=10) # 日志工具栏 log_toolbar = ttk.Frame(log_frame) log_toolbar.pack(fill=tk.X, padx=5, pady=5) ttk.Button(log_toolbar, text="清空日志", command=self.clear_log, style="Small.TButton").pack(side=tk.LEFT, padx=2) ttk.Button(log_toolbar, text="复制日志", command=self.copy_log, style="Small.TButton").pack(side=tk.LEFT, padx=2) # OCR安装按钮 ttk.Button( log_toolbar, text="安装OCR指南", command=self.install_tesseract, style="Small.TButton" ).pack(side=tk.LEFT, padx=2) self.log_text = scrolledtext.ScrolledText( log_frame, wrap=tk.WORD, state=tk.DISABLED, bg="#fafafa", relief="solid", borderwidth=1 ) self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # 绑定快捷键 self.root.bind('<F9>', lambda e: self.toggle_start()) self.root.bind('<F10>', lambda e: self.stop_typing()) self.root.bind('<F11>', lambda e: self.open_content_dialog()) # 免责声明 - 放在整个窗口最底部 disclaimer_frame = ttk.Frame(main_frame) disclaimer_frame.pack(fill=tk.X, padx=10, pady=15) disclaimer_label = tk.Label( disclaimer_frame, text="本作者开发的初衷是和朋友游玩,他人行为与本作者没有任何关系,仅供学习游玩", font=("Microsoft YaHei", 17), fg="#e74c3c", justify=tk.CENTER, bg="#f9f9f9" ) disclaimer_label.pack(fill=tk.X, pady=2) # 版权信息 copyright_frame = ttk.Frame(main_frame) copyright_frame.pack(fill=tk.X, padx=10, pady=(0, 5)) copyright_label = ttk.Label( copyright_frame, text="© 2024 超级打字助手 Pro", style="Copyright.TLabel", cursor="hand2" ) copyright_label.pack(side=tk.RIGHT) copyright_label.bind("<Button-1>", lambda e: self.show_about()) # 设置焦点 self.root.focus_set() # 绑定按钮事件以改变边框颜色 self.bind_button_events() def update_ocr_status(self): """更新 OCR 状态显示""" if self.tesseract_installed: self.ocr_status_var.set("OCR状态: 已安装") else: self.ocr_status_var.set("OCR状态: 未安装") def bind_button_events(self): """绑定按钮事件以改变边框颜色""" # 开始按钮 self.start_btn.bind("<ButtonPress-1>", lambda e: self.start_btn.config(highlightbackground="blue")) self.start_btn.bind("<ButtonRelease-1>", lambda e: self.start_btn.config(highlightbackground="black")) # 停止按钮 self.stop_btn.bind("<ButtonPress-1>", lambda e: self.stop_btn.config(highlightbackground="blue")) self.stop_btn.bind("<ButtonRelease-1>", lambda e: self.stop_btn.config(highlightbackground="black")) # 随机模式按钮 self.random_mode_btn.bind("<ButtonPress-1>", lambda e: self.random_mode_btn.config(highlightbackground="blue")) self.random_mode_btn.bind("<ButtonRelease-1>", lambda e: self.random_mode_btn.config(highlightbackground="black")) # 文字识别按钮 self.text_detect_btn.bind("<ButtonPress-1>", lambda e: self.text_detect_btn.config(highlightbackground="blue")) self.text_detect_btn.bind("<ButtonRelease-1>", lambda e: self.text_detect_btn.config(highlightbackground="black")) def toggle_text_detection(self): """切换文字识别模式""" if not self.tesseract_installed: self.install_tesseract() return self.text_detection_enabled = not self.text_detection_enabled if self.text_detection_enabled: self.text_detect_btn.config( text="文字识别: 开启", bg="#4CAF50", # 绿色背景 fg="white" ) self.fast_log("🔍 文字识别模式已开启") # 启动文字识别线程 threading.Thread(target=self.detect_text_and_click, daemon=True).start() else: self.text_detect_btn.config( text="文字识别: 关闭", bg="#f0f0f0", # 默认灰色背景 fg="black" ) self.fast_log("🔍 文字识别模式已关闭") def detect_text_and_click(self): """检测屏幕上的文字并模拟点击""" while self.text_detection_enabled: try: # 截取整个屏幕 screenshot = ImageGrab.grab() # 使用OCR识别文字 import pytesseract text = pytesseract.image_to_string(screenshot, lang='chi_sim') # 检查是否包含目标文字 if self.text_to_detect in text: self.fast_log(f"✅ 检测到文字 '{self.text_to_detect}',模拟点击屏幕") # 获取屏幕中心位置 screen_width, screen_height = pyautogui.size() center_x = screen_width // 2 center_y = screen_height // 2 # 模拟点击 pyautogui.click(center_x, center_y) # 等待5秒 time.sleep(self.text_detection_interval) except Exception as e: self.fast_log(f"❌ 文字识别失败: {e}") time.sleep(5) def toggle_random_mode(self): """切换随机输出模式""" self.random_output_mode = not self.random_output_mode if self.random_output_mode: self.random_mode_btn.config( text="随机模式: 开启", bg="#4CAF50", # 绿色背景 fg="white" ) self.fast_log("🎲 随机输出模式已开启") else: self.random_mode_btn.config( text="随机模式: 关闭", bg="#f0f0f0", # 默认灰色背景 fg="black" ) self.fast_log("📝 顺序输出模式已开启") # 更新内容显示 self.update_content_display() def setup_styles(self): """设置界面样式""" style = ttk.Style() # 配置不同样式 style.configure("Title.TLabel", font=("Microsoft YaHei", 16, "bold"), foreground="#2c3e50") style.configure("Version.TLabel", font=("Microsoft YaHei", 10), foreground="#7f8c8d") style.configure("Mode.TLabel", font=("Microsoft YaHei", 9, "bold"), foreground="#2980b9") style.configure("Bold.TLabel", font=("Microsoft YaHei", 10, "bold")) style.configure("Small.TLabel", font=("Microsoft YaHei", 8)) style.configure("Status.TLabel", font=("Microsoft YaHei", 9)) style.configure("Copyright.TLabel", font=("Microsoft YaHei", 8), foreground="#3498db") style.configure("TButton", padding=6) style.configure("Small.TButton", font=("Microsoft YaHei", 8), padding=3) style.configure("TLabelFrame", font=("Microsoft YaHei", 10, "bold")) style.configure("TLabelFrame.Label", font=("Microsoft YaHei", 10, "bold")) def show_about(self): """显示关于对话框""" messagebox.showinfo("关于", "超级打字助手 Pro v4.2\n" "作者: 云醉\n" "邮箱: 1153430448@qq.com\n\n" "功能特点:\n" "- 自动输入文本\n" "- 可自定义输入内容\n" "- 高级文字识别功能\n" "- 随机输出模式\n" "- 多频率控制\n\n" "注意: 文字识别功能需要安装 Tesseract OCR" ) def update_mode_display(self): """更新模式显示""" mode_text = "最强模式" self.mode_label.config(text=f"模式: {mode_text}") def update_frequency(self, value): """更新发送频率""" self.interval = float(value) self.freq_var.set(f"当前频率: 每{self.interval:.2f}秒发送一条") def set_preset_frequency(self, interval): """设置预设频率""" self.freq_scale.set(interval) self.interval = interval self.freq_var.set(f"当前频率: 每{self.interval:.2f}秒发送一条") self.fast_log(f"📶 预设频率已设置: 每{self.interval:.2f}秒发送一条") def update_content_display(self): """更新内容显示区域""" self.content_text.config(state=tk.NORMAL) self.content_text.delete(1.0, tk.END) content_list = self.custom_content if self.custom_content else self.default_content if self.random_output_mode: self.content_text.insert(tk.END, "【随机输出模式】\n") random.shuffle(content_list) for i, content in enumerate(content_list[:self.max_content_items]): self.content_text.insert(tk.END, f"{i+1}. {content}\n") self.content_text.config(state=tk.DISABLED) def open_content_dialog(self): """打开内容设置对话框""" dialog = tk.Toplevel(self.root) dialog.title("设置输入内容") dialog.geometry("500x400") dialog.resizable(True, True) dialog.transient(self.root) dialog.grab_set() # 主框架 main_frame = ttk.Frame(dialog, padding=15) main_frame.pack(fill=tk.BOTH, expand=True) # 提示文字 prompt_label = ttk.Label( main_frame, text="请输入要自动输入的内容(每行一条):", justify=tk.LEFT ) prompt_label.pack(anchor=tk.W, pady=(0, 5)) # 内容输入框 content_entry = scrolledtext.ScrolledText( main_frame, wrap=tk.WORD, height=12 ) content_entry.pack(fill=tk.BOTH, expand=True, pady=5) # 填充当前内容 content_list = self.custom_content if self.custom_content else self.default_content content_entry.insert(tk.END, "\n".join(content_list)) # 按钮框架 button_frame = ttk.Frame(main_frame) button_frame.pack(fill=tk.X, pady=(10, 0)) def save_content(): content = content_entry.get(1.0, tk.END).strip() if content: self.custom_content = [line.strip() for line in content.split('\n') if line.strip()] self.fast_log(f"✅ 已保存 {len(self.custom_content)} 条自定义内容") self.update_content_display() dialog.destroy() ttk.Button(button_frame, text="保存", command=save_content, width=10).pack(side=tk.RIGHT, padx=5) ttk.Button(button_frame, text="取消", command=dialog.destroy, width=10).pack(side=tk.RIGHT) def toggle_start(self): """切换开始/停止状态""" if not self.running_flag: self.start_typing() else: self.stop_typing() def start_typing(self): """开始自动输入""" self.running_flag = True self.start_btn.config(state=tk.DISABLED) self.stop_btn.config(state=tk.NORMAL) self.status_var.set("运行中...") self.fast_log("▶️ 开始自动输入") # 启动输入线程 threading.Thread(target=self.type_content, daemon=True).start() def stop_typing(self): """停止自动输入""" self.running_flag = False self.start_btn.config(state=tk.NORMAL) self.stop_btn.config(state=tk.DISABLED) self.status_var.set("已停止") self.fast_log("⏹️ 停止自动输入") def type_content(self): """自动输入内容""" content_list = self.custom_content if self.custom_content else self.default_content if not content_list: self.fast_log("⚠️ 没有可输入的内容") self.stop_typing() return # 如果开启了随机模式,打乱内容顺序 if self.random_output_mode: content_list = content_list.copy() random.shuffle(content_list) self.fast_log(f"📝 准备输入 {len(content_list)} 条内容") # 获取焦点到目标应用 self.fast_log("⚠️ 请确保目标应用窗口已激活") time.sleep(2) # 给用户时间切换窗口 # 开始输入循环 while self.running_flag: for content in content_list[:self.max_content_items]: if not self.running_flag: break try: # 复制内容到剪贴板 pyperclip.copy(content) # 粘贴内容 pyautogui.hotkey('ctrl', 'v') self.fast_log(f"📤 输入: {content}") # 发送(回车键) pyautogui.press('enter') # 等待指定间隔 time.sleep(self.interval) except Exception as e: self.fast_log(f"❌ 输入失败: {e}") time.sleep(1) def fast_log(self, message): """快速日志记录(使用队列,减少GUI更新)""" current_time = time.time() # 如果是高频日志,进行批量处理 if current_time - self.last_log_time < 0.1 and "输入:" in message: self.log_batch.append(message) if len(self.log_batch) >= 5: batch_msg = f"📤 批量输入: {len(self.log_batch)}条消息..." self.log_queue.put(batch_msg) self.log_batch.clear() else: if self.log_batch: batch_msg = f"📤 批量输入: {len(self.log_batch)}条消息..." self.log_queue.put(batch_msg) self.log_batch.clear() self.log_queue.put(message) self.last_log_time = current_time def process_log_queue(self): """处理日志队列(减少GUI更新频率)""" try: while True: message = self.log_queue.get_nowait() self.log_text.config(state=tk.NORMAL) self.log_text.insert(tk.END, f"{time.strftime('%H:%M:%S')} - {message}\n") self.log_text.see(tk.END) self.log_text.config(state=tk.DISABLED) # 更新状态栏(仅重要消息) if "✅" in message or "❌" in message or "🛑" in message: self.status_var.set(message.split(" - ")[-1]) except queue.Empty: pass self.root.after(100, self.process_log_queue) # 每100ms处理一次 def clear_log(self): """清空日志""" self.log_text.config(state=tk.NORMAL) self.log_text.delete(1.0, tk.END) self.log_text.config(state=tk.DISABLED) self.fast_log("🧹 日志已清空") def copy_log(self): """复制日志内容""" log_content = self.log_text.get(1.0, tk.END) pyperclip.copy(log_content) self.fast_log("📋 日志已复制到剪贴板") def on_closing(self): """关闭窗口时的处理""" self.running_flag = False # 停止任何正在进行的输入 self.text_detection_enabled = False # 停止文字识别 self.root.destroy() # 程序入口 if __name__ == "__main__": app = SuperTyperPro() app.root.mainloop()
最新发布
11-18
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、在编辑脚本时,做一个快捷添加常用快捷键功能。
06-21
#include <stdio.h> #include <stdint.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" #include "driver/spi_master.h" static const char *TAG = "GMD1032"; // ============================= // SPI 引脚定义(根据实际接线修改) // ============================= #define PIN_MOSI 19 #define PIN_MISO 16 #define PIN_CLK 18 #define PIN_CS 17 // 自定义错误码:使用保留范围 0x20000+ #ifndef ESP_ERR_CRC #define ESP_ERR_CRC (0x20000 + 0) // CRC 校验失败 #endif #ifndef ESP_ERR_SENSOR_COMM #define ESP_ERR_SENSOR_COMM (0x20000 + 1) // 通信故障 #endif #ifndef ESP_ERR_DATA_INVALID #define ESP_ERR_DATA_INVALID (0x20000 + 2) // 数据格式错误 #endif // SPI 总线和设备句柄 spi_device_handle_t gmd1032_spi; // ============================= // CRC-8 计算函数(Poly=0x07, Init=0x41) // ============================= uint8_t crc8_gmd1032(uint8_t data) { uint8_t crc = 0x41; // 初始值 const uint8_t poly = 0x07; // X^8 + X^2 + X + 1 for (int i = 0; i < 8; i++) { if (((crc >> 7) ^ (data >> 7)) & 0x01) { crc <<= 1; crc ^= poly; } else { crc <<= 1; } data <<= 1; } return crc; } // 构造带 CRC-8 的 16 位命令 #define MAKE_CMD(cmd) (((uint16_t)(cmd) << 8) | crc8_gmd1032(cmd)) // 常用命令宏(已验证) #define CMD_ENTER_MEASURE_MODE MAKE_CMD(0x2A) // 0x2A16 #define CMD_RESTART_ADC MAKE_CMD(0x2E) // 0x2E0A #define CMD_READ_CFG0 MAKE_CMD(0xC0) // 0xC08E #define CMD_READ_STS0 MAKE_CMD(0xC9) // 0xC9B1 #define CMD_READ_CELL1_3 MAKE_CMD(0xD0) // 0xD0FE // ============================= // CRC-10 计算函数(Poly=0x233, Init=0x10) // ============================= uint16_t crc10_gmd1032(uint64_t data_54bits) { uint16_t crc = 0x10; // 初始值 const uint16_t poly = 0x233; // X^10 + X^7 + X^3 + X^2 + X + 1 data_54bits &= 0x3FFFFFFFFFFFFULL; // 只保留低 54 位 for (int i = 0; i < 54; i++) { if (((crc >> 9) & 1) ^ ((data_54bits >> 53) & 1)) { crc = (crc << 1) ^ poly; } else { crc <<= 1; } crc &= 0x3FF; // 保持 10 位 data_54bits <<= 1; } return crc; } // ============================= // 解析 GMD1032 返回的 64 位数据帧 // ============================= typedef struct { uint64_t data_48; // 48-bit 数据(如电压) uint8_t rolling_count; // 6-bit 滚动计数器 uint16_t crc10; // 10-bit 接收到的 CRC } gmd1032_data_frame_t; bool parse_and_validate_frame(const uint8_t *rx_buf, gmd1032_data_frame_t *frame) { // 合并为大端序 64 位 uint64_t raw = 0; for (int i = 0; i < 8; i++) { raw = (raw << 8) | rx_buf[i]; } frame->data_48 = (raw >> 16) & 0xFFFFFFFFFFFFULL; // 高48位 frame->rolling_count = (raw >> 10) & 0x3F; // 中间6位 frame->crc10 = raw & 0x3FF; // 低10位 // 验证 CRC-10 uint64_t combined = (frame->data_48 << 6) | frame->rolling_count; uint16_t expected = crc10_gmd1032(combined); if (expected != frame->crc10) { ESP_LOGW(TAG, "CRC-10 校验失败!期望=0x%03X, 实际=0x%03X", expected, frame->crc10); return false; } return true; } // ============================= // SPI 初始化 // ============================= esp_err_t spi_init() { esp_err_t ret; // 初始化 SPI 总线(SPI3_HOST = VSPI) spi_bus_config_t buscfg = { .mosi_io_num = PIN_MOSI, .miso_io_num = PIN_MISO, .sclk_io_num = PIN_CLK, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = 8, // 最大支持 8 字节接收 }; ret = spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO); if (ret != ESP_OK) { ESP_LOGE(TAG, "SPI 总线初始化失败: %s", esp_err_to_name(ret)); return ret; } // 配置设备 spi_device_interface_config_t devcfg = { .command_bits = 16, // 命令长度 16 bit .address_bits = 0, .mode = 0, // CPOL=0, CPHA=0 .clock_speed_hz = 1 * 1000 * 1000, // 1MHz .spics_io_num = PIN_CS, // CS 引脚 .queue_size = 4, }; ret = spi_bus_add_device(SPI3_HOST, &devcfg, &gmd1032_spi); if (ret != ESP_OK) { ESP_LOGE(TAG, "SPI 设备创建失败: %s", esp_err_to_name(ret)); return ret; } ESP_LOGI(TAG, "SPI 初始化成功"); return ESP_OK; } // ============================= // 发送命令(仅发送,无返回数据) // ============================= esp_err_t gmd1032_send_command(uint16_t cmd16) { spi_transaction_t t = { .cmd = cmd16, .length = 0, // 不需要额外数据 }; esp_err_t ret = spi_device_transmit(gmd1032_spi, &t); if (ret == ESP_OK) { ESP_LOGD(TAG, "成功发送命令: 0x%04X", cmd16); } else { ESP_LOGE(TAG, "命令发送失败: %s", esp_err_to_name(ret)); } return ret; } // ============================= // 发送命令并读取 8 字节响应 // ============================= esp_err_t gmd1032_read_data_frame(uint16_t cmd16, gmd1032_data_frame_t *out_frame) { uint8_t rx_data[8]; spi_transaction_t t = { .cmd = cmd16, .rx_buffer = rx_data, .length = 64, // 接收 64 bits = 8 bytes .rxlength = 64, }; esp_err_t ret = spi_device_transmit(gmd1032_spi, &t); if (ret != ESP_OK) { ESP_LOGE(TAG, "SPI 读取失败: %s", esp_err_to_name(ret)); return ret; } if (!parse_and_validate_frame(rx_data, out_frame)) { return ESP_ERR_CRC; } ESP_LOGI(TAG, "数据接收成功: 0x%012llX, RC=%u", out_frame->data_48, out_frame->rolling_count); return ESP_OK; } // ============================= // 主任务:驱动 GMD1032 读取电池电压 // ============================= void gmd1032_voltage_task(void *arg) { gmd1032_data_frame_t frame; uint16_t cell_mv[3]; // 存储三节电芯电压(单位 mV) ESP_LOGI(TAG, "开始 GMD1032 电池电压采集任务..."); // 初始化 SPI if (spi_init() != ESP_OK) { ESP_LOGE(TAG, "SPI 初始化失败,停止任务"); vTaskDelete(NULL); return; } // 步骤1: 进入测量状态 if (gmd1032_send_command(CMD_ENTER_MEASURE_MODE) != ESP_OK) { ESP_LOGE(TAG, "进入测量模式失败"); } vTaskDelay(pdMS_TO_TICKS(10)); // 步骤2: 重启 ADC if (gmd1032_send_command(CMD_RESTART_ADC) != ESP_OK) { ESP_LOGE(TAG, "重启 ADC 失败"); } vTaskDelay(pdMS_TO_TICKS(10)); while (1) { ESP_LOGI(TAG, "正在读取 Cell1-3 电压..."); if (gmd1032_read_data_frame(CMD_READ_CELL1_3, &frame) == ESP_OK) { // 假设每 16 位表示一个电压原始值(具体格式请参考手册) uint16_t raw1 = (frame.data_48 >> 32) & 0xFFFF; uint16_t raw2 = (frame.data_48 >> 16) & 0xFFFF; uint16_t raw3 = frame.data_48 & 0xFFFF; // 示例转换:假设满量程 5V 对应 0xFFFF → 单位 mV // 实际比例需根据 GMD1032 内部增益和参考电压调整 cell_mv[0] = (raw1 * 5000ULL) / 65535; cell_mv[1] = (raw2 * 5000ULL) / 65535; cell_mv[2] = (raw3 * 5000ULL) / 65535; ESP_LOGI("CELL_VOLTAGE", "Cell1: %d mV, Cell2: %d mV, Cell3: %d mV", cell_mv[0], cell_mv[1], cell_mv[2]); } else { ESP_LOGW(TAG, "读取电压失败,可能是通信异常"); } vTaskDelay(pdMS_TO_TICKS(1000)); // 每秒采样一次 } } // ============================= // 入口函数 // ============================= void app_main(void) { xTaskCreate(gmd1032_voltage_task, "gmd1032_task", 4096, NULL, 10, NULL); } 上述代码运行结果如下: I (5316) GMD1032: 正在读取 Cell1-3 电压... W (5316) GMD1032: CRC-10 校验失败!期望=0x38C, 实际=0x000 W (5316) GMD1032: 读取电压失败,可能是通信异常
09-23
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值