| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- import tkinter as tk
- from tkinter import ttk
- import time
- from datetime import datetime, timedelta
- import threading
- import queue
- import ctypes
- import sys
- class PreciseTimerGUI:
- def __init__(self):
- self.root = tk.Tk()
- self.root.title("精准定时按键工具")
- self.root.geometry("500x300")
- self.root.resizable(False, False)
-
- # 设置窗口图标和置顶
- self.root.attributes('-topmost', True)
-
- # 变量定义
- self.target_time = None
- self.is_running = False
- self.update_queue = queue.Queue()
-
- # 按键映射(实际发送虚拟按键需要对应键盘码)
- self.key_map = {
- "1": 0x31, "2": 0x32, "3": 0x33, "4": 0x34,
- "9": 0x39, "D": 0x44, "M": 0x4D, "O": 0x4F,
- "P": 0x50, "回车": 0x0D
- }
-
- self.setup_ui()
- self.update_time()
- self.check_timer()
-
- def setup_ui(self):
- """设置用户界面"""
- # 主框架
- main_frame = ttk.Frame(self.root, padding="20")
- main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
-
- # 当前时间显示
- ttk.Label(main_frame, text="当前系统时间:", font=("Arial", 10)).grid(row=0, column=0, sticky=tk.W, pady=(0, 5))
-
- self.time_var = tk.StringVar()
- self.time_label = ttk.Label(main_frame, textvariable=self.time_var, font=("Courier", 24, "bold"))
- self.time_label.grid(row=1, column=0, columnspan=2, pady=(0, 20))
-
- # 分隔线
- ttk.Separator(main_frame, orient='horizontal').grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 20))
-
- # 定时设置区域
- ttk.Label(main_frame, text="定时设置:", font=("Arial", 10, "bold")).grid(row=3, column=0, sticky=tk.W, pady=(0, 10))
-
- # 时间输入区域
- time_frame = ttk.Frame(main_frame)
- time_frame.grid(row=4, column=0, columnspan=2, pady=(0, 10))
-
- ttk.Label(time_frame, text="目标时间:").pack(side=tk.LEFT, padx=(0, 5))
-
- # 时分秒毫秒输入框
- self.hour_var = tk.StringVar(value="00")
- self.min_var = tk.StringVar(value="00")
- self.sec_var = tk.StringVar(value="00")
- self.ms_var = tk.StringVar(value="000")
-
- hour_spin = ttk.Spinbox(time_frame, from_=0, to=23, width=3, textvariable=self.hour_var, format="%02.0f")
- hour_spin.pack(side=tk.LEFT, padx=2)
- ttk.Label(time_frame, text=":").pack(side=tk.LEFT)
-
- min_spin = ttk.Spinbox(time_frame, from_=0, to=59, width=3, textvariable=self.min_var, format="%02.0f")
- min_spin.pack(side=tk.LEFT, padx=2)
- ttk.Label(time_frame, text=":").pack(side=tk.LEFT)
-
- sec_spin = ttk.Spinbox(time_frame, from_=0, to=59, width=3, textvariable=self.sec_var, format="%02.0f")
- sec_spin.pack(side=tk.LEFT, padx=2)
- ttk.Label(time_frame, text=".").pack(side=tk.LEFT)
-
- ms_spin = ttk.Spinbox(time_frame, from_=0, to=999, width=4, textvariable=self.ms_var, format="%03.0f")
- ms_spin.pack(side=tk.LEFT, padx=2)
-
- # 按键选择
- key_frame = ttk.Frame(main_frame)
- key_frame.grid(row=5, column=0, columnspan=2, pady=(0, 10))
-
- ttk.Label(key_frame, text="触发按键:").pack(side=tk.LEFT, padx=(0, 10))
-
- self.key_var = tk.StringVar(value="回车")
- key_combo = ttk.Combobox(key_frame, textvariable=self.key_var, values=list(self.key_map.keys()), width=10, state="readonly")
- key_combo.pack(side=tk.LEFT)
-
- # 按钮区域
- button_frame = ttk.Frame(main_frame)
- button_frame.grid(row=6, column=0, columnspan=2, pady=(10, 0))
-
- self.start_button = ttk.Button(button_frame, text="启动定时", command=self.start_timer, width=12)
- self.start_button.pack(side=tk.LEFT, padx=5)
-
- self.stop_button = ttk.Button(button_frame, text="停止定时", command=self.stop_timer, state=tk.DISABLED, width=12)
- self.stop_button.pack(side=tk.LEFT, padx=5)
-
- ttk.Button(button_frame, text="设置当前时间", command=self.set_current_time, width=15).pack(side=tk.LEFT, padx=5)
-
- # 状态显示
- self.status_var = tk.StringVar(value="就绪")
- status_label = ttk.Label(main_frame, textvariable=self.status_var, font=("Arial", 9), foreground="gray")
- status_label.grid(row=7, column=0, columnspan=2, pady=(20, 0))
-
- def update_time(self):
- """更新时间显示(精确到毫秒)"""
- now = datetime.now()
- time_str = now.strftime("%Y-%m-%d %H:%M:%S") + f".{now.microsecond//1000:03d}"
- self.time_var.set(time_str)
-
- # 使用after方法实现低CPU占用的定时更新
- self.root.after(10, self.update_time)
-
- def set_current_time(self):
- """将目标时间设置为当前时间"""
- now = datetime.now()
- self.hour_var.set(f"{now.hour:02d}")
- self.min_var.set(f"{now.minute:02d}")
- self.sec_var.set(f"{now.second:02d}")
- self.ms_var.set(f"{(now.microsecond//1000):03d}")
- self.status_var.set("已设置为当前时间")
- self.root.after(2000, lambda: self.status_var.set("就绪"))
-
- def start_timer(self):
- """启动定时器"""
- try:
- # 解析目标时间
- hour = int(self.hour_var.get())
- minute = int(self.min_var.get())
- second = int(self.sec_var.get())
- millisecond = int(self.ms_var.get())
-
- now = datetime.now()
- target = now.replace(hour=hour, minute=minute, second=second, microsecond=millisecond*1000)
-
- # 如果目标时间小于当前时间,设置为明天
- if target <= now:
- target += timedelta(days=1)
-
- self.target_time = target
- self.is_running = True
-
- self.start_button.config(state=tk.DISABLED)
- self.stop_button.config(state=tk.NORMAL)
-
- # 显示目标时间和倒计时
- time_remaining = (target - now).total_seconds()
- self.status_var.set(f"定时已启动,目标时间: {target.strftime('%H:%M:%S')}.{millisecond:03d},剩余: {time_remaining:.2f}秒")
-
- except ValueError:
- self.status_var.set("请输入有效的时间")
-
- def stop_timer(self):
- """停止定时器"""
- self.is_running = False
- self.target_time = None
- self.start_button.config(state=tk.NORMAL)
- self.stop_button.config(state=tk.DISABLED)
- self.status_var.set("定时已停止")
-
- def check_timer(self):
- """检查定时是否到达"""
- if self.is_running and self.target_time:
- now = datetime.now()
- if now >= self.target_time:
- self.trigger_key()
- self.stop_timer()
- self.status_var.set("定时已触发!")
- self.root.after(3000, lambda: self.status_var.set("就绪"))
-
- # 更新倒计时显示
- if self.is_running and self.target_time:
- now = datetime.now()
- if now < self.target_time:
- remaining = (self.target_time - now).total_seconds()
- if remaining <= 5: # 最后5秒显示毫秒
- self.status_var.set(f"即将触发!剩余: {remaining:.3f}秒")
- elif remaining <= 60:
- self.status_var.set(f"剩余: {remaining:.1f}秒")
- else:
- minutes = int(remaining // 60)
- seconds = int(remaining % 60)
- self.status_var.set(f"目标时间: {self.target_time.strftime('%H:%M:%S')},剩余: {minutes}分{seconds}秒")
-
- # 每10毫秒检查一次,确保毫秒级精度
- self.root.after(10, self.check_timer)
-
- def trigger_key(self):
- """触发按键(跨平台支持)"""
- key_name = self.key_var.get()
- key_code = self.key_map.get(key_name)
-
- if key_code:
- # 在单独线程中执行按键操作,避免阻塞UI
- threading.Thread(target=self._send_key, args=(key_code,), daemon=True).start()
-
- def _send_key(self, key_code):
- """实际发送按键(Windows平台)"""
- try:
- if sys.platform == "win32":
- # Windows平台使用keybd_event
- import ctypes
- from ctypes import wintypes
-
- # 定义常量
- KEYEVENTF_KEYDOWN = 0x0000
- KEYEVENTF_KEYUP = 0x0002
-
- # 发送按键按下
- ctypes.windll.user32.keybd_event(key_code, 0, KEYEVENTF_KEYDOWN, 0)
- time.sleep(0.02) # 短暂延时模拟真实按键
- # 发送按键释放
- ctypes.windll.user32.keybd_event(key_code, 0, KEYEVENTF_KEYUP, 0)
-
- # 更新状态显示(通过队列跨线程)
- self.root.after(0, lambda: self.status_var.set(f"已触发按键: {self.key_var.get()}"))
- else:
- print(f"触发按键: {self.key_var.get()} (仅Windows平台支持真实按键)")
- self.root.after(0, lambda: self.status_var.set(f"模拟触发按键: {self.key_var.get()}"))
-
- except Exception as e:
- print(f"按键触发失败: {e}")
- self.root.after(0, lambda: self.status_var.set(f"按键触发失败: {str(e)}"))
-
- def run(self):
- """运行应用"""
- self.root.mainloop()
- if __name__ == "__main__":
- app = PreciseTimerGUI()
- app.run()
|