定时按某键.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. import tkinter as tk
  2. from tkinter import ttk
  3. import time
  4. from datetime import datetime, timedelta
  5. import threading
  6. import queue
  7. import ctypes
  8. import sys
  9. class PreciseTimerGUI:
  10. def __init__(self):
  11. self.root = tk.Tk()
  12. self.root.title("Microsoft")
  13. self.root.geometry("620x380")
  14. self.root.resizable(False, False)
  15. # 取消窗口置顶
  16. # self.root.attributes('-topmost', True) # 已注释,不再置顶
  17. # 变量定义
  18. self.target_time = None
  19. self.is_running = False
  20. self.update_queue = queue.Queue()
  21. self.time_locked = False # 时间锁定标志
  22. # 按键映射
  23. self.key_map = {
  24. "1": 0x31, "2": 0x32, "3": 0x33, "4": 0x34,
  25. "9": 0x39, "D": 0x44, "M": 0x4D, "O": 0x4F,
  26. "P": 0x50, "回车": 0x0D
  27. }
  28. self.setup_ui()
  29. self.bind_events()
  30. self.update_time()
  31. self.check_timer()
  32. def setup_ui(self):
  33. """设置用户界面"""
  34. # 主框架
  35. main_frame = ttk.Frame(self.root, padding="15")
  36. main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  37. # 当前系统时间显示(放到下面)
  38. ttk.Label(main_frame, text="当前系统时间:", font=("Arial", 10, "bold")).grid(row=3, column=0, sticky=tk.W, pady=(20, 5))
  39. self.time_var = tk.StringVar()
  40. self.time_label = ttk.Label(main_frame, textvariable=self.time_var, font=("Courier", 28, "bold"), foreground="blue")
  41. self.time_label.grid(row=4, column=0, columnspan=3, pady=(0, 20))
  42. # 分隔线
  43. ttk.Separator(main_frame, orient='horizontal').grid(row=5, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 15))
  44. # 定时设置区域(上面)
  45. ttk.Label(main_frame, text="定时设置:", font=("Arial", 10, "bold")).grid(row=0, column=0, sticky=tk.W, pady=(0, 10))
  46. # 时间输入区域
  47. time_frame = ttk.Frame(main_frame)
  48. time_frame.grid(row=1, column=0, columnspan=2, pady=(0, 10), sticky=tk.W)
  49. ttk.Label(time_frame, text="目标时间:").pack(side=tk.LEFT, padx=(0, 8))
  50. # 时、分、秒、毫秒输入框
  51. self.hour_var = tk.StringVar(value="00")
  52. self.min_var = tk.StringVar(value="00")
  53. self.sec_var = tk.StringVar(value="00")
  54. self.ms_var = tk.StringVar(value="000")
  55. # 时
  56. self.hour_spin = ttk.Spinbox(time_frame, from_=0, to=23, width=3, textvariable=self.hour_var, format="%02.0f")
  57. self.hour_spin.pack(side=tk.LEFT, padx=2)
  58. ttk.Label(time_frame, text=":").pack(side=tk.LEFT)
  59. # 分
  60. self.min_spin = ttk.Spinbox(time_frame, from_=0, to=59, width=3, textvariable=self.min_var, format="%02.0f")
  61. self.min_spin.pack(side=tk.LEFT, padx=2)
  62. ttk.Label(time_frame, text=":").pack(side=tk.LEFT)
  63. # 秒
  64. self.sec_spin = ttk.Spinbox(time_frame, from_=0, to=59, width=3, textvariable=self.sec_var, format="%02.0f")
  65. self.sec_spin.pack(side=tk.LEFT, padx=2)
  66. ttk.Label(time_frame, text=".").pack(side=tk.LEFT)
  67. # 毫秒
  68. self.ms_spin = ttk.Spinbox(time_frame, from_=0, to=999, width=4, textvariable=self.ms_var, format="%03.0f")
  69. self.ms_spin.pack(side=tk.LEFT, padx=2)
  70. # 按键选择区域(与定时设置同一排)
  71. key_frame = ttk.Frame(main_frame)
  72. key_frame.grid(row=1, column=2, pady=(0, 10), sticky=tk.W, padx=(20, 0))
  73. ttk.Label(key_frame, text="触发按键:").pack(side=tk.LEFT, padx=(0, 8))
  74. self.key_var = tk.StringVar(value="回车")
  75. self.key_combo = ttk.Combobox(key_frame, textvariable=self.key_var, values=list(self.key_map.keys()), width=8, state="readonly")
  76. self.key_combo.pack(side=tk.LEFT)
  77. # 按钮区域
  78. button_frame = ttk.Frame(main_frame)
  79. button_frame.grid(row=2, column=0, columnspan=3, pady=(10, 15))
  80. self.start_button = ttk.Button(button_frame, text="▶ 启动定时", command=self.start_timer, width=12)
  81. self.start_button.pack(side=tk.LEFT, padx=5)
  82. self.stop_button = ttk.Button(button_frame, text="■ 停止定时", command=self.stop_timer, state=tk.DISABLED, width=12)
  83. self.stop_button.pack(side=tk.LEFT, padx=5)
  84. ttk.Button(button_frame, text="设置当前时间", command=self.set_current_time, width=13).pack(side=tk.LEFT, padx=5)
  85. # 状态显示区域(使用Label直接设置样式)
  86. self.status_var = tk.StringVar(value="就绪")
  87. self.status_label = tk.Label(main_frame, textvariable=self.status_var,
  88. font=("Arial", 12, "bold"),
  89. fg="red", # 默认红色
  90. bg=self.root.cget("bg"))
  91. self.status_label.grid(row=6, column=0, columnspan=3, pady=(10, 0))
  92. def bind_events(self):
  93. """绑定回车键切换输入框事件"""
  94. # 为每个输入框绑定回车事件,清空下一个输入框的值
  95. self.hour_spin.bind("<Return>", self.on_hour_enter)
  96. self.min_spin.bind("<Return>", self.on_min_enter)
  97. self.sec_spin.bind("<Return>", self.on_sec_enter)
  98. self.ms_spin.bind("<Return>", self.on_ms_enter)
  99. self.key_combo.bind("<Return>", lambda e: self.start_timer())
  100. # 绑定启动定时快捷键(Ctrl+S)
  101. self.root.bind("<Control-s>", lambda e: self.start_timer())
  102. # 绑定停止定时快捷键(Ctrl+E)
  103. self.root.bind("<Control-e>", lambda e: self.stop_timer())
  104. # 绑定输入框获得焦点时自动全选
  105. for widget in [self.hour_spin, self.min_spin, self.sec_spin, self.ms_spin]:
  106. widget.bind("<FocusIn>", self.select_all_text)
  107. def select_all_text(self, event):
  108. """选中输入框中的所有文本"""
  109. event.widget.select_range(0, tk.END)
  110. event.widget.icursor(tk.END)
  111. return "break"
  112. def on_hour_enter(self, event):
  113. """小时输入框按回车"""
  114. # 清空分钟输入框的值
  115. # self.min_var.set("00")
  116. # 光标移到分钟输入框
  117. self.min_spin.focus_set()
  118. return "break"
  119. def on_min_enter(self, event):
  120. """分钟输入框按回车"""
  121. # 清空秒输入框的值
  122. # self.sec_var.set("00")
  123. # 光标移到秒输入框
  124. self.sec_spin.focus_set()
  125. return "break"
  126. def on_sec_enter(self, event):
  127. """秒输入框按回车"""
  128. # 清空毫秒输入框的值
  129. # self.ms_var.set("000")
  130. # 光标移到毫秒输入框
  131. self.ms_spin.focus_set()
  132. return "break"
  133. def on_ms_enter(self, event):
  134. """毫秒输入框按回车"""
  135. # 清空按键选择的值
  136. self.key_var.set("回车")
  137. # 光标移到按键选择框
  138. self.key_combo.focus_set()
  139. return "break"
  140. def update_time(self):
  141. """更新时间显示(精确到毫秒)"""
  142. now = datetime.now()
  143. time_str = now.strftime("%Y-%m-%d %H:%M:%S") + f".{now.microsecond//1000:03d}"
  144. self.time_var.set(time_str)
  145. # 使用after方法实现低CPU占用的定时更新
  146. self.root.after(10, self.update_time)
  147. def set_current_time(self):
  148. """将目标时间设置为当前时间"""
  149. # 如果定时已启动,禁止修改时间
  150. if self.time_locked:
  151. self.status_var.set("定时已启动,请先停止定时再修改时间")
  152. self.root.after(2000, lambda: self.update_status_text())
  153. return
  154. now = datetime.now()
  155. self.hour_var.set(f"{now.hour:02d}")
  156. self.min_var.set(f"{now.minute:02d}")
  157. self.sec_var.set(f"{now.second:02d}")
  158. self.ms_var.set(f"{(now.microsecond//1000):03d}")
  159. self.status_var.set("已设置为当前时间")
  160. self.root.after(2000, lambda: self.update_status_text())
  161. def update_status_text(self):
  162. """恢复状态文本"""
  163. if not self.is_running:
  164. self.status_var.set("就绪")
  165. self.status_label.config(fg="red")
  166. def start_timer(self):
  167. """启动定时器"""
  168. # 如果已经启动,禁止修改时间
  169. if self.is_running:
  170. self.status_var.set("定时已运行中,请先停止")
  171. self.root.after(2000, lambda: self.update_status_text())
  172. return
  173. try:
  174. # 解析目标时间
  175. hour = int(self.hour_var.get())
  176. minute = int(self.min_var.get())
  177. second = int(self.sec_var.get())
  178. millisecond = int(self.ms_var.get())
  179. # 验证时间有效性
  180. if not (0 <= hour <= 23 and 0 <= minute <= 59 and 0 <= second <= 59 and 0 <= millisecond <= 999):
  181. raise ValueError("时间超出范围")
  182. now = datetime.now()
  183. target = now.replace(hour=hour, minute=minute, second=second, microsecond=millisecond*1000)
  184. # 如果目标时间小于等于当前时间,设置为明天
  185. if target <= now:
  186. target += timedelta(days=1)
  187. self.target_time = target
  188. self.is_running = True
  189. self.time_locked = True # 锁定时间,禁止修改
  190. # 禁用时间输入框
  191. self.hour_spin.config(state=tk.DISABLED)
  192. self.min_spin.config(state=tk.DISABLED)
  193. self.sec_spin.config(state=tk.DISABLED)
  194. self.ms_spin.config(state=tk.DISABLED)
  195. self.start_button.config(state=tk.DISABLED)
  196. self.stop_button.config(state=tk.NORMAL)
  197. # 显示目标时间和倒计时
  198. time_remaining = (target - now).total_seconds()
  199. self.status_var.set(f"目标时间: {target.strftime('%H:%M:%S')}.{millisecond:03d},剩余: {time_remaining:.2f}秒")
  200. self.status_label.config(fg="red", font=("Arial", 12, "bold")) # 始终保持红色加粗
  201. except ValueError as e:
  202. self.status_var.set(f"请输入有效的时间: {str(e)}")
  203. self.root.after(2000, lambda: self.update_status_text())
  204. def stop_timer(self):
  205. """停止定时器"""
  206. self.is_running = False
  207. self.time_locked = False # 解锁时间
  208. self.target_time = None
  209. # 启用时间输入框
  210. self.hour_spin.config(state=tk.NORMAL)
  211. self.min_spin.config(state=tk.NORMAL)
  212. self.sec_spin.config(state=tk.NORMAL)
  213. self.ms_spin.config(state=tk.NORMAL)
  214. self.start_button.config(state=tk.NORMAL)
  215. self.stop_button.config(state=tk.DISABLED)
  216. self.status_var.set("定时已停止")
  217. self.status_label.config(fg="red", font=("Arial", 12, "bold"))
  218. self.root.after(2000, lambda: self.update_status_text())
  219. def check_timer(self):
  220. """检查定时是否到达"""
  221. if self.is_running and self.target_time:
  222. now = datetime.now()
  223. if now >= self.target_time:
  224. self.trigger_key()
  225. self.stop_timer()
  226. self.status_var.set("✓ 定时已触发!")
  227. self.status_label.config(fg="green", font=("Arial", 12, "bold"))
  228. self.root.after(3000, lambda: self.update_status_text())
  229. # 更新倒计时显示(始终保持红色)
  230. if self.is_running and self.target_time:
  231. now = datetime.now()
  232. if now < self.target_time:
  233. remaining = (self.target_time - now).total_seconds()
  234. # 格式化显示剩余时间
  235. if remaining <= 5: # 最后5秒显示毫秒
  236. remaining_ms = (self.target_time - now).total_seconds()
  237. status_msg = f"目标时间: {self.target_time.strftime('%H:%M:%S')}.{self.target_time.microsecond//1000:03d},⚠ 即将触发!剩余: {remaining_ms:.3f}秒"
  238. else:
  239. minutes = int(remaining // 60)
  240. seconds = int(remaining % 60)
  241. ms = int((remaining - int(remaining)) * 1000)
  242. if minutes > 0:
  243. status_msg = f"目标时间: {self.target_time.strftime('%H:%M:%S')}.{self.target_time.microsecond//1000:03d},剩余: {minutes}分{seconds}秒"
  244. else:
  245. status_msg = f"目标时间: {self.target_time.strftime('%H:%M:%S')}.{self.target_time.microsecond//1000:03d},剩余: {remaining:.2f}秒"
  246. self.status_var.set(status_msg)
  247. self.status_label.config(fg="red", font=("Arial", 12, "bold")) # 始终保持红色加粗
  248. # 每10毫秒检查一次,确保毫秒级精度
  249. self.root.after(10, self.check_timer)
  250. def trigger_key(self):
  251. """触发按键"""
  252. key_name = self.key_var.get()
  253. key_code = self.key_map.get(key_name)
  254. if key_code:
  255. # 在单独线程中执行按键操作,避免阻塞UI
  256. threading.Thread(target=self._send_key, args=(key_code,), daemon=True).start()
  257. def _send_key(self, key_code):
  258. """实际发送按键(Windows平台)"""
  259. try:
  260. if sys.platform == "win32":
  261. # Windows平台使用keybd_event
  262. # 发送按键按下
  263. ctypes.windll.user32.keybd_event(key_code, 0, 0x0000, 0)
  264. time.sleep(0.02) # 短暂延时模拟真实按键
  265. # 发送按键释放
  266. ctypes.windll.user32.keybd_event(key_code, 0, 0x0002, 0)
  267. # 更新状态显示
  268. self.root.after(0, lambda: self.status_var.set(f"✓ 已触发按键: {self.key_var.get()}"))
  269. self.root.after(0, lambda: self.status_label.config(fg="green", font=("Arial", 12, "bold")))
  270. else:
  271. print(f"触发按键: {self.key_var.get()} (仅Windows平台支持真实按键)")
  272. self.root.after(0, lambda: self.status_var.set(f"模拟触发按键: {self.key_var.get()}"))
  273. except Exception as e:
  274. print(f"按键触发失败: {e}")
  275. self.root.after(0, lambda: self.status_var.set(f"按键触发失败: {str(e)}"))
  276. def run(self):
  277. """运行应用"""
  278. self.root.mainloop()
  279. if __name__ == "__main__":
  280. app = PreciseTimerGUI()
  281. app.run()