滚轮每次滚动距离是一致的吗_关于滚动条需要考虑的事儿:高精度鼠标的滚轮...

本文讨论了鼠标滚轮处理中的挑战,特别是如何处理高精度鼠标的半程滚动现象,并提出了相应的解决方案。

鼠标滚轮的处理

鼠标滚轮的处理会稍微棘手一点,因为在UI设计规范中,当用户滑动鼠标滚轮的时候,需要将内容视图滚动一个预定义的”量”,这里的”量”实际上是一个WHEEL_DELTA(也叫detent)的值。
在上面的需求中,有两个不容易察觉的地方:第一,具体需要滚动的量来自于一个系统设定,我们必须遵从这个设定,第二,某些鼠标会报告给操作系统:它的滚动量不是WHEEL_DELTA的整数倍。
这就尴尬了!

所谓的”半程滚动”

特别的是,存在一种可能性,某一类高精度鼠标上报的滚动量会小于WHEEL_DELTA。举个例子,考虑一种支持”half-clicks”的鼠标。当你在滑动滚轮的量位于”点击”的一半时,它会上报滚动量为 WHEEL_DELTA/2,当你继续滚动, 直到滑动量等效于一次点击时,它会再次上报剩下的WHEEL_DELTA/2。为了处理这种特别情况,你需要确保当滚动量到达一次点击时,窗口内容必须和普通低分辨率鼠标完成单个WHEEL_DELTA滚动时产生一致的表现。

为了处理上文中的第一个问题,我们会在每次收到鼠标滚轮消息的时候查询用户期望的滚动Delta值。对于第二个问题,我们可以将这些滚动消息积累起来,然后尽可能多的处理它们,为后续的滚动事件预留时间。

直接上代码

541740cdc745819c186342a02e644df6.png

/****************华丽分割****************/

df2e553841d417550a3672a42374b4d1.png

家庭作业

1) 在上述代码中,我们在计算dLines的时候,使用了一个int类型转换,为什么要怎么做?
2) 如果你手头没有一个高精度滚轮鼠标,你会如何测试半滚动时代码是否能正常工作?

总结

再一次为微软在硬件兼容性上所做的重重努力所折服,她是如此的关心你:不管你使用的是一个普通鼠标,还是一个高精度鼠标。

4c40698f7d84f3b7da4790186b3cbdc3.png
import os import sys import tempfile import tkinter as tk from tkinter import filedialog, messagebox, colorchooser, ttk from io import BytesIO import re from datetime import datetime import traceback # 添加缺失的导入 import fitz # PyMuPDF class PDFDateHighlighter: def __init__(self, root): self.root = root self.root.title("PDF日期高亮与打印工具") self.root.geometry("1200x700") # 增大窗口以适应两页显示 # 指定日期列表 self.specified_dates = [ "2025-01-01", "2025-01-27", "2025-01-28", "2025-01-29", "2025-03-28", "2025-03-29", "2025-03-31", "2025-04-01", "2025-04-02", "2025-04-03", "2025-04-04", "2025-04-07", "2025-04-18", "2025-04-20", "2025-05-01", "2025-05-12", "2025-05-13", "2025-05-29", "2025-05-30", "2025-06-01", "2025-06-06", "2025-06-09", "2025-06-27", "2025-08-17", "2025-09-05", "2025-12-25", "2025-12-26" ] self.pdf_path = None self.highlighted_pdf_path = None self.current_page = 0 self.total_pages = 0 self.pdf_document = None # 使用荧光颜色 self.highlight_color = (1.0, 1.0, 0.0) # 荧光黄色 RGB self.highlight_alpha = 50 # 降低不透明度以确保文字可见 self.current_images = [] # 保持对图像的引用(两页) # 手动涂改相关变量 self.drawing_mode = False self.erasing_mode = False self.start_x = None self.start_y = None self.brush_width = 10 # 默认笔宽 self.drawing_items = [] # 存储绘制的项目用于撤回 self.current_drawing = [] # 当前绘制的项目 self.setup_ui() def setup_ui(self): # 主框架 main_frame = ttk.Frame(self.root, padding="10") main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) # 配置行列权重 self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=1) main_frame.columnconfigure(1, weight=1) main_frame.rowconfigure(3, weight=1) # 文件选择区域 ttk.Label(main_frame, text="PDF文件:").grid(row=0, column=0, sticky=tk.W, pady=5) self.file_path = ttk.Entry(main_frame, width=80) self.file_path.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5, padx=5) ttk.Button(main_frame, text="浏览", command=self.browse_file).grid(row=0, column=2, pady=5) # 按钮区域 btn_frame = ttk.Frame(main_frame) btn_frame.grid(row=1, column=0, columnspan=3, pady=10) # 添加撤回按钮 ttk.Button(btn_frame, text="撤回", command=self.undo_drawing).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="处理PDF", command=self.process_pdf).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="上一页", command=self.prev_page).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="下一页", command=self.next_page).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="打印", command=self.print_pdf).pack(side=tk.LEFT, padx=5) # 颜色选择按钮和图标 ttk.Button(btn_frame, text="选择高亮颜色", command=self.choose_color).pack(side=tk.LEFT, padx=5) # 颜色图标 self.color_icon = tk.Canvas(btn_frame, width=20, height=20, bg="#ffff00") # 荧光黄色 self.color_icon.pack(side=tk.LEFT, padx=5) self.color_icon.bind("<Button-1>", lambda e: self.choose_color()) # 手动涂改按钮 ttk.Button(btn_frame, text="手动涂改", command=self.toggle_drawing).pack(side=tk.LEFT, padx=5) # 笔宽设置 ttk.Label(btn_frame, text="笔宽:").pack(side=tk.LEFT, padx=5) self.brush_width_var = tk.StringVar(value="15") brush_width_spin = ttk.Spinbox(btn_frame, from_=1, to=20, width=5, textvariable=self.brush_width_var) brush_width_spin.pack(side=tk.LEFT, padx=5) brush_width_spin.bind("<<Increment>>", self.update_brush_width) brush_width_spin.bind("<<Decrement>>", self.update_brush_width) # 橡皮擦按钮 ttk.Button(btn_frame, text="橡皮擦", command=self.toggle_erasing).pack(side=tk.LEFT, padx=5) # 页面显示区域 self.page_label = ttk.Label(main_frame, text="页面: 0/0") self.page_label.grid(row=2, column=0, columnspan=3, pady=5) # PDF显示区域 - 改为两页平行显示 self.pdf_frame = ttk.Frame(main_frame) self.pdf_frame.grid(row=3, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=10) self.pdf_frame.columnconfigure(0, weight=1) self.pdf_frame.columnconfigure(1, weight=1) self.pdf_frame.rowconfigure(0, weight=1) # 创建两个Canvas用于显示两页 self.canvas1 = tk.Canvas(self.pdf_frame, bg="white") self.canvas2 = tk.Canvas(self.pdf_frame, bg="white") # 添加滚动条 - 改为快速翻页滚动条 self.page_scrollbar = ttk.Scrollbar(self.pdf_frame, orient=tk.VERTICAL) self.h_scrollbar = ttk.Scrollbar(self.pdf_frame, orient=tk.HORIZONTAL) # 配置Canvas和滚动条 self.canvas1.configure(xscrollcommand=self.h_scrollbar.set) self.canvas2.configure(xscrollcommand=self.h_scrollbar.set) self.h_scrollbar.configure(command=self.sync_scroll_x) # 配置页面滚动条 self.page_scrollbar.configure(command=self.scroll_pages) # 布局 self.canvas1.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 5)) self.canvas2.grid(row=0, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(5, 0)) self.page_scrollbar.grid(row=0, column=2, sticky=(tk.N, tk.S)) self.h_scrollbar.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E)) # 绑定鼠标滚轮事件 self.canvas1.bind("<MouseWheel>", self.on_mousewheel) self.canvas2.bind("<MouseWheel>", self.on_mousewheel) self.canvas1.bind("<Button-4>", self.on_mousewheel) # Linux向上滚动 self.canvas2.bind("<Button-4>", self.on_mousewheel) self.canvas1.bind("<Button-5>", self.on_mousewheel) # Linux向下滚动 self.canvas2.bind("<Button-5>", self.on_mousewheel) # 绑定手动涂改事件 self.canvas1.bind("<ButtonPress-1>", self.start_drawing) self.canvas1.bind("<B1-Motion>", self.draw) self.canvas1.bind("<ButtonRelease-1>", self.stop_drawing) self.canvas2.bind("<ButtonPress-1>", self.start_drawing) self.canvas2.bind("<B1-Motion>", self.draw) self.canvas2.bind("<ButtonRelease-1>", self.stop_drawing) # 状态栏 self.status_bar = ttk.Label(self.root, text="就绪", relief=tk.SUNKEN, anchor=tk.W) self.status_bar.grid(row=4, column=0, sticky=(tk.W, tk.E)) def sync_scroll_x(self, *args): """同步两个Canvas的水平滚动""" self.canvas1.xview(*args) self.canvas2.xview(*args) def scroll_pages(self, *args): """处理页面滚动条事件""" if not self.pdf_document or self.total_pages == 0: return # 获取滚动条位置 if len(args) > 1 and args[0] == "moveto": scroll_pos = float(args[1]) elif len(args) > 2 and args[0] == "scroll": units = int(args[1]) scroll_pos = float(args[2]) else: return # 计算目标页面 target_page = int(scroll_pos * (self.total_pages - 1)) # 确保目标页面是偶数(因为每次显示两页) if target_page % 2 != 0: target_page = max(0, target_page - 1) # 更新当前页面 if target_page != self.current_page: self.current_page = target_page self.display_page() def update_scrollbar(self): """更新滚动条位置""" if not self.pdf_document or self.total_pages == 0: return # 计算滚动条位置 scroll_pos = self.current_page / (self.total_pages - 1) if self.total_pages > 1 else 0 # 更新滚动条 self.page_scrollbar.set(scroll_pos, scroll_pos + 1/(self.total_pages)) def on_mousewheel(self, event): """处理鼠标滚轮事件""" if event.num == 4 or event.delta > 0: # 向上滚动 self.prev_page() elif event.num == 5 or event.delta < 0: # 向下滚动 self.next_page() def toggle_drawing(self): """切换手动涂改模式""" self.drawing_mode = not self.drawing_mode self.erasing_mode = False # 确保橡皮擦模式关闭 if self.drawing_mode: self.status_bar.config(text="手动涂改模式已启用 - 点击并拖动以涂改") else: self.status_bar.config(text="手动涂改模式已禁用") def toggle_erasing(self): """切换橡皮擦模式""" self.erasing_mode = not self.erasing_mode self.drawing_mode = False # 确保涂改模式关闭 if self.erasing_mode: self.status_bar.config(text="橡皮擦模式已启用 - 点击并拖动以擦除") else: self.status_bar.config(text="橡皮擦模式已禁用") def update_brush_width(self, event): """更新笔宽""" try: self.brush_width = int(self.brush_width_var.get()) except ValueError: self.brush_width = 5 self.brush_width_var.set("5") def start_drawing(self, event): """开始手动涂改""" if not self.drawing_mode and not self.erasing_mode: return self.start_x = event.x self.start_y = event.y self.current_drawing = [] def draw(self, event): """手动涂改或擦除""" if (not self.drawing_mode and not self.erasing_mode) or self.start_x is None or self.start_y is None: return # 获取当前Canvas current_canvas = event.widget # 确定颜色 - 涂改模式使用荧光色,擦除模式使用白色 if self.drawing_mode: # 使用荧光色,但降低不透明度以确保文字可见 color = "#{:02x}{:02x}{:02x}".format( int(self.highlight_color[0] * 255), int(self.highlight_color[1] * 255), int(self.highlight_color[2] * 255) ) # 创建半透明矩形 - 使用点状图案实现半透明效果 rect_id = current_canvas.create_rectangle( self.start_x, self.start_y, event.x, event.y, fill=color, outline="", stipple="gray50" # 使用点状图案实现半透明效果 ) self.current_drawing.append(rect_id) else: # 擦除模式 # 查找并删除与橡皮擦重叠的手动涂改 items = current_canvas.find_overlapping( event.x - self.brush_width, event.y - self.brush_width, event.x + self.brush_width, event.y + self.brush_width ) for item in items: if item in self.get_all_drawing_items(): current_canvas.delete(item) # 从所有存储的绘制项目中移除 self.remove_drawing_item(item) self.start_x = event.x self.start_y = event.y def get_all_drawing_items(self): """获取所有绘制项目的ID""" all_items = [] for drawing in self.drawing_items: all_items.extend(drawing) return all_items def remove_drawing_item(self, item_id): """从存储的绘制项目中移除指定ID""" for i, drawing in enumerate(self.drawing_items): if item_id in drawing: drawing.remove(item_id) if not drawing: # 如果绘图为空,移除整个绘图 self.drawing_items.pop(i) break def stop_drawing(self, event): """停止手动涂改""" if self.current_drawing and self.drawing_mode: # 只在涂改模式下保存绘制项目 self.drawing_items.append(self.current_drawing.copy()) self.start_x = None self.start_y = None self.current_drawing = [] def undo_drawing(self): """撤回最后一次涂改""" if not self.drawing_items: return # 获取最后一次涂改的项目 last_drawing = self.drawing_items.pop() # 从两个Canvas中删除这些项目 for item_id in last_drawing: self.canvas1.delete(item_id) self.canvas2.delete(item_id) self.status_bar.config(text="已撤回最后一次涂改") def choose_color(self): """允许用户选择高亮颜色""" color = colorchooser.askcolor(title="选择高亮颜色", initialcolor="#ffff00") if color[0]: # 用户选择了颜色 r, g, b = color[0] self.highlight_color = (r/255, g/255, b/255) # 转换为0-1范围 # 更新颜色图标 self.color_icon.config(bg=color[1]) def browse_file(self): try: file_path = filedialog.askopenfilename(filetypes=[("PDF文件", "*.pdf")]) if file_path: self.file_path.delete(0, tk.END) self.file_path.insert(0, file_path) self.pdf_path = file_path self.load_pdf() except Exception as e: messagebox.showerror("错误", f"浏览文件时发生错误: {str(e)}") def load_pdf(self): if not self.pdf_path: return try: if self.pdf_document: self.pdf_document.close() self.pdf_document = fitz.open(self.pdf_path) self.total_pages = len(self.pdf_document) self.current_page = 0 self.display_page() self.update_scrollbar() except Exception as e: messagebox.showerror("错误", f"无法打开PDF文件: {str(e)}") def display_page(self): if not self.pdf_document or self.total_pages == 0: return self.page_label.config(text=f"页面: {self.current_page+1}-{min(self.current_page+2, self.total_pages)}/{self.total_pages}") try: # 清空当前图像引用 self.current_images = [] # 显示第一页 if self.current_page < self.total_pages: page1 = self.pdf_document[self.current_page] zoom = 1.0 # 缩小一点以适应两页显示 mat = fitz.Matrix(zoom, zoom) pix1 = page1.get_pixmap(matrix=mat) # 转换为PhotoImage img_data1 = pix1.tobytes("ppm") img1 = tk.PhotoImage(data=img_data1) self.current_images.append(img1) # 保持引用 # 更新Canvas self.canvas1.config(width=min(img1.width(), 550), height=min(img1.height(), 500)) self.canvas1.delete("all") self.canvas1.create_image(0, 0, anchor=tk.NW, image=img1) # 设置滚动区域 self.canvas1.config(scrollregion=self.canvas1.bbox(tk.ALL)) # 显示第二页(如果有) if self.current_page + 1 < self.total_pages: page2 = self.pdf_document[self.current_page + 1] zoom = 1.0 # 缩小一点以适应两页显示 mat = fitz.Matrix(zoom, zoom) pix2 = page2.get_pixmap(matrix=mat) # 转换为PhotoImage img_data2 = pix2.tobytes("ppm") img2 = tk.PhotoImage(data=img_data2) self.current_images.append(img2) # 保持引用 # 更新Canvas self.canvas2.config(width=min(img2.width(), 550), height=min(img2.height(), 500)) self.canvas2.delete("all") self.canvas2.create_image(0, 0, anchor=tk.NW, image=img2) # 设置滚动区域 self.canvas2.config(scrollregion=self.canvas2.bbox(tk.ALL)) else: # 如果没有第二页,清空第二个Canvas self.canvas2.delete("all") self.canvas2.config(width=0, height=0) # 更新滚动条位置 self.update_scrollbar() except Exception as e: messagebox.showerror("错误", f"显示页面时发生错误: {str(e)}") def prev_page(self): if self.current_page > 0: self.current_page = max(0, self.current_page - 2) self.display_page() def next_page(self): if self.pdf_document and self.current_page + 2 < self.total_pages: self.current_page += 2 self.display_page() def process_pdf(self): if not self.pdf_path: messagebox.showwarning("警告", "请先选择PDF文件") return self.status_bar.config(text="正在处理PDF...") self.root.update() try: # 创建临时文件保存处理后的PDF temp_file = tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) self.highlighted_pdf_path = temp_file.name temp_file.close() # 打开原始PDF doc = fitz.open(self.pdf_path) # 遍历每一页 for page_num in range(len(doc)): page = doc[page_num] # 获取页面文本 - 使用更精确的搜索方法 text = page.get_text("text") # 查找所有日期格式的文本 date_pattern = r"\d{4}-\d{2}-\d{2}" dates = re.findall(date_pattern, text) print(f"在第 {page_num+1} 页找到的日期: {dates}") # 调试信息 # 对于每个找到的日期,查找其在页面上的位置并高亮 for date_str in dates: if self.should_highlight(date_str): print(f"高亮日期: {date_str}") # 调试信息 # 搜索日期文本的位置 text_instances = page.search_for(date_str) print(f"找到 {len(text_instances)} 个匹配项") # 调试信息 for inst in text_instances: # 使用高亮注释 highlight = page.add_highlight_annot(inst) # 设置荧光颜色 highlight.set_colors(stroke=self.highlight_color) highlight.set_opacity(self.highlight_alpha) highlight.update() # 保存处理后的PDF doc.save(self.highlighted_pdf_path) doc.close() # 重新加载处理后的PDF if self.pdf_document: self.pdf_document.close() self.pdf_document = fitz.open(self.highlighted_pdf_path) self.total_pages = len(self.pdf_document) self.current_page = 0 self.display_page() self.status_bar.config(text="处理完成") messagebox.showinfo("成功", "PDF处理完成,日期已高亮显示") except Exception as e: self.status_bar.config(text="处理失败") error_msg = f"处理PDF时发生错误: {str(e)}\n\n详细信息:\n{traceback.format_exc()}" messagebox.showerror("错误", error_msg) def should_highlight(self, date_str): # 检查是否为指定日期 if date_str in self.specified_dates: return True # 检查是否为周日 try: date_obj = datetime.strptime(date_str, "%Y-%m-%d") if date_obj.weekday() == 6: # 周日是6 return True except ValueError: pass return False def print_pdf(self): if not self.highlighted_pdf_path or not os.path.exists(self.highlighted_pdf_path): messagebox.showwarning("警告", "请先处理PDF文件") return try: # 使用系统默认程序打开PDF进行打印 if sys.platform == "win32": os.startfile(self.highlighted_pdf_path, "print") elif sys.platform == "darwin": # macOS os.system(f"lpr '{self.highlighted_pdf_path}'") else: # Linux os.system(f"xdg-open '{self.highlighted_pdf_path}'") self.status_bar.config(text="打印任务已发送") except Exception as e: messagebox.showerror("错误", f"打印失败: {str(e)}") def __del__(self): # 清理临时文件 if self.highlighted_pdf_path and os.path.exists(self.highlighted_pdf_path): try: os.unlink(self.highlighted_pdf_path) except: pass # 关闭PDF文档 if self.pdf_document: try: self.pdf_document.close() except: pass def main(): try: # 检查是否安装了PyMuPDF try: import fitz except ImportError: messagebox.showerror("缺少依赖", "请安装PyMuPDF库: pip install PyMuPDF") return root = tk.Tk() app = PDFDateHighlighter(root) root.mainloop() except Exception as e: error_msg = f"程序发生未预期错误: {str(e)}\n\n详细信息:\n{traceback.format_exc()}" messagebox.showerror("严重错误", error_msg) if __name__ == "__main__": main() 手动涂改时涂改的粗细未按笔宽输入的值增粗或减细,涂改的笔宽默认为10mm 点击“处理PDF”按钮后未自动对PDF中所有的图片进行扫描并拾取查找对应的日期及周日日期进行自动涂上颜色,如“2025-06-27”未被自动涂改上颜色
最新发布
08-22
import os import json import time import random import threading import datetime import pyautogui import keyboard from tkinter import * from tkinter import ttk, messagebox, filedialog from tkinter.font import Font class ActionRecorder: def __init__(self): self.recording = False self.playing = False self.actions = [] self.current_config = { "random_interval": 100, "loop_times": 1, "script_name": "未命名脚本" } self.configs = {} self.load_configs() # 创建日志目录 if not os.path.exists("logs"): os.makedirs("logs") # 初始化GUI self.init_gui() # 设置快捷键 self.setup_hotkeys() # 鼠标和键盘状态跟踪 self.mouse_state = { "left": False, "right": False, "middle": False } self.keyboard_state = {} # 鼠标和键盘监听器 self.keyboard_hook = None self.mouse_hook = None # 忽略的按键 self.ignored_keys = {'f1', 'f2'} def init_gui(self): self.root = Tk() self.root.title("动作录制器 v3.3") 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="随机间隔(ms):", font=normal_font).grid(row=1, column=0, sticky="e", pady=5) self.interval_entry = Entry(config_frame, font=normal_font) self.interval_entry.grid(row=1, column=1, sticky="we", padx=5, pady=5) self.interval_entry.insert(0, str(self.current_config["random_interval"])) # 循环次数 Label(config_frame, text="循环次数:", font=normal_font).grid(row=2, column=0, sticky="e", pady=5) self.loop_entry = Entry(config_frame, font=normal_font) self.loop_entry.grid(row=2, column=1, sticky="we", padx=5, pady=5) self.loop_entry.insert(0, str(self.current_config["loop_times"])) # 按钮区域 button_frame = Frame(self.root) button_frame.pack(fill="x", padx=10, pady=10) self.record_btn = Button(button_frame, text="开始/停止录制 (F1)", command=self.toggle_recording, font=bold_font, bg="#4CAF50", fg="white") self.record_btn.pack(side="left", padx=5, ipadx=10, ipady=5) self.play_btn = Button(button_frame, text="开始/停止执行 (F2)", command=self.toggle_playing, font=bold_font, bg="#2196F3", fg="white") self.play_btn.pack(side="left", padx=5, ipadx=10, ipady=5) self.save_btn = Button(button_frame, text="保存脚本", command=self.save_script, font=bold_font, bg="#9C27B0", fg="white") self.save_btn.pack(side="right", padx=5, ipadx=10, ipady=5) self.delete_btn = Button(button_frame, text="删除脚本", command=self.delete_script, font=bold_font, bg="#607D8B", fg="white") self.delete_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() self.status_var.set("就绪") 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 setup_hotkeys(self): keyboard.add_hotkey('f1', self.toggle_recording, suppress=True) keyboard.add_hotkey('f2', self.toggle_playing, suppress=True) def toggle_recording(self): if self.recording: self.stop_recording() else: self.start_recording() def toggle_playing(self): if self.playing: self.stop_playing() else: self.start_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" with open(log_file, "a", encoding="utf-8") as f: f.write(log_entry) def save_configs(self): with open("configs.json", "w", encoding="utf-8") as f: json.dump(self.configs, f, ensure_ascii=False, indent=2) 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 update_current_config(self): try: self.current_config["script_name"] = self.script_name_entry.get() self.current_config["random_interval"] = int(self.interval_entry.get()) self.current_config["loop_times"] = int(self.loop_entry.get()) return True except ValueError: messagebox.showerror("错误", "请输入有效的数字") return False def save_script(self): if not self.update_current_config(): return if not self.actions: messagebox.showwarning("警告", "没有录制的动作可以保存") return script_name = self.current_config["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}' 已保存") 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}' 已删除") 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"] # 更新UI self.script_name_entry.delete(0, END) self.script_name_entry.insert(0, self.current_config["script_name"]) self.interval_entry.delete(0, END) self.interval_entry.insert(0, str(self.current_config["random_interval"])) self.loop_entry.delete(0, END) self.loop_entry.insert(0, str(self.current_config["loop_times"])) self.log_message(f"已加载脚本 '{script_name}'") self.log_message(f"共 {len(self.actions)} 个动作") def start_recording(self): if self.recording or self.playing: return if not self.update_current_config(): return self.recording = True self.actions = [] self.record_btn.config(bg="#F44336", text="停止录制 (F1)") self.play_btn.config(state=DISABLED) self.save_btn.config(state=DISABLED) self.delete_btn.config(state=DISABLED) # 重置鼠标和键盘状态 self.mouse_state = { "left": False, "right": False, "middle": False } self.keyboard_state = {} # 设置键盘和鼠标钩子 self.keyboard_hook = keyboard.hook(self.on_key_event) self.mouse_hook = keyboard.hook(self.on_mouse_event) self.log_message("开始录制...") self.log_message("请开始您的操作,按F1停止录制") self.log_message("正在录制: 鼠标点击、滚轮和键盘操作") def stop_recording(self): if not self.recording: return self.recording = False self.record_btn.config(bg="#4CAF50", text="开始录制 (F1)") self.play_btn.config(state=NORMAL) self.save_btn.config(state=NORMAL) self.delete_btn.config(state=NORMAL) # 移除钩子 if self.keyboard_hook: keyboard.unhook(self.keyboard_hook) if self.mouse_hook: keyboard.unhook(self.mouse_hook) self.log_message(f"停止录制,共录制了 {len(self.actions)} 个动作") self.save_script() def on_mouse_event(self, event): if not self.recording: return current_pos = pyautogui.position() if event.event_type in ('down', 'up'): button_mapping = { "left": "left", "right": "right", "middle": "middle", "x1": "x1", "x2": "x2" } button = button_mapping.get(event.name, "left") # 默认使用左键 action_type = "mousedown" if event.event_type == 'down' else "mouseup" self.actions.append({ "type": action_type, "button": button, "x": current_pos.x, "y": current_pos.y, "time": time.time() }) action_desc = f"鼠标{'按下' if event.event_type == 'down' else '释放'}: {button}键" self.log_message(f"{action_desc} 位置: ({current_pos.x}, {current_pos.y})") elif event.event_type == 'wheel': direction = "上" if event.delta > 0 else "下" self.actions.append({ "type": "wheel", "delta": event.delta, "x": current_pos.x, "y": current_pos.y, "time": time.time() }) self.log_message(f"滚轮滚动: 方向 {direction} 位置: ({current_pos.x}, {current_pos.y})") def on_key_event(self, event): if not self.recording: return # 忽略F1/F2按键 if event.name.lower() in self.ignored_keys: return if event.event_type == "down": # 只记录第一次按下,不记录重复按下 if event.name not in self.keyboard_state: self.keyboard_state[event.name] = True self.actions.append({ "type": "keydown", "key": event.name, "time": time.time() }) self.log_message(f"按键按下: {event.name}") elif event.event_type == "up": if event.name in self.keyboard_state: del self.keyboard_state[event.name] self.actions.append({ "type": "keyup", "key": event.name, "time": time.time() }) self.log_message(f"按键释放: {event.name}") def start_playing(self): if self.playing or self.recording or not self.actions: return if not self.update_current_config(): return self.playing = True self.play_btn.config(bg="#FF9800", text="停止执行 (F2)") self.record_btn.config(state=DISABLED) self.save_btn.config(state=DISABLED) self.delete_btn.config(state=DISABLED) # 开始执行线程 threading.Thread(target=self.play_actions, daemon=True).start() self.log_message("开始执行脚本...") def stop_playing(self): if not self.playing: return self.playing = False self.play_btn.config(bg="#2196F3", text="开始执行 (F2)") self.record_btn.config(state=NORMAL) self.save_btn.config(state=NORMAL) self.delete_btn.config(state=NORMAL) self.log_message("停止执行脚本") def play_actions(self): loop_times = self.current_config["loop_times"] random_interval = self.current_config["random_interval"] / 1000.0 # 转换为秒 for loop in range(loop_times): if not self.playing: break self.log_message(f"开始第 {loop + 1} 次循环 (共 {loop_times} 次)") prev_time = None for i, action in enumerate(self.actions): if not self.playing: break # 计算延迟 if prev_time is not None: delay = action["time"] - prev_time # 添加随机间隔 if random_interval > 0: delay += random.uniform(0, random_interval) time.sleep(max(0, delay)) prev_time = action["time"] # 执行动作 try: if action["type"] == "mousedown": # 确保只使用有效的按钮参数 button = action["button"] if action["button"] in ('left', 'middle', 'right') else 'left' pyautogui.mouseDown(x=action["x"], y=action["y"], button=button) self.log_message( f"执行动作 {i + 1}/{len(self.actions)}: 在({action['x']}, {action['y']})按下 {button}键") elif action["type"] == "mouseup": button = action["button"] if action["button"] in ('left', 'middle', 'right') else 'left' pyautogui.mouseUp(x=action["x"], y=action["y"], button=button) self.log_message( f"执行动作 {i + 1}/{len(self.actions)}: 在({action['x']}, {action['y']})释放 {button}键") elif action["type"] == "wheel": pyautogui.scroll(action["delta"]) self.log_message( f"执行动作 {i + 1}/{len(self.actions)}: 滚轮滚动 {'上' if action['delta'] > 0 else '下'}") elif action["type"] == "keydown": keyboard.press(action["key"]) self.log_message(f"执行动作 {i + 1}/{len(self.actions)}: 按下 {action['key']}键") elif action["type"] == "keyup": keyboard.release(action["key"]) self.log_message(f"执行动作 {i + 1}/{len(self.actions)}: 释放 {action['key']}键") except Exception as e: self.log_message(f"执行动作时出错: {str(e)}") if loop < loop_times - 1 and self.playing: time.sleep(1) # 循环之间的间隔 self.stop_playing() def run(self): self.root.mainloop() if __name__ == "__main__": recorder = ActionRecorder() recorder.run() 请帮我检查这个键盘鼠标脚本录制器错误地方,并帮我修改!
06-19
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值