[Python] - import tkinter as tk
- from tkinter import ttk, messagebox, filedialog
- import time
- import threading
- import os
- import subprocess
- import winreg
- import sys
- from datetime import datetime, timedelta
- import msvcrt
- class TimedTaskTool:
- def __init__(self, root):
- # 检查是否已经有实例在运行
- self.check_single_instance()
- self.root = root
- self.root.title("定时任务工具")
- self.root.geometry("500x720") # 增加宽度以显示所有星期
- self.root.resizable(False, False)
- # 设置中文字体,适当缩小
- self.style = ttk.Style()
- self.style.configure("TLabel", font=("SimHei", 8))
- self.style.configure("TRadiobutton", font=("SimHei", 8))
- self.style.configure("TButton", font=("SimHei", 8))
- self.style.configure("TCheckbutton", font=("SimHei", 8))
- # 操作模式
- self.operation_mode = tk.StringVar(value="重启")
- self.create_operation_mode_frame()
- # 日期设置
- self.day_of_month = tk.IntVar(value=15)
- self.week_days = {"周一": tk.BooleanVar(), "周二": tk.BooleanVar(),
- "周三": tk.BooleanVar(), "周四": tk.BooleanVar(),
- "周五": tk.BooleanVar(), "周六": tk.BooleanVar(),
- "周日": tk.BooleanVar()}
- self.create_date_frame()
- # 时间设置
- self.hour = tk.IntVar(value=12)
- self.minute = tk.IntVar(value=0)
- self.create_time_frame()
- # 任务存储 - 支持多个定时任务
- self.tasks = {}
- self.task_threads = {}
- self.task_statuses = {}
- for mode in ["重启", "关机", "休眠"]:
- self.tasks[mode] = None
- self.task_threads[mode] = None
- self.task_statuses[mode] = tk.StringVar(value=f"{mode}任务: 未设置")
- # 重复频率
- self.repeat_type = tk.StringVar(value="每日")
- self.create_repeat_frame()
- # 倒计时操作
- self.countdown_minutes = tk.IntVar(value=1)
- self.countdown_status = tk.StringVar(value="未启动")
- self.countdown_label = None # 用于控制颜色
- self.create_countdown_frame()
- # 开机设置
- self.startup_auto = tk.BooleanVar(value=False)
- self.startup_program = tk.StringVar(value="未选择任何项目")
- self.startup_check_var = tk.BooleanVar(value=False)
- self.create_startup_frame()
- # 任务按钮
- self.create_task_buttons()
- # 任务状态
- self.create_status_label()
- # 检查开机自启动状态
- self.check_startup_status()
- def check_single_instance(self):
- # 使用文件锁实现单实例
- self.lock_file = os.path.join(os.environ.get('TEMP', 'C:\\temp'), 'timed_task_tool.lock')
- try:
- # 尝试创建并锁定文件
- self.lock = open(self.lock_file, 'w')
- msvcrt.locking(self.lock.fileno(), msvcrt.LK_NBLCK, 1)
- except:
- messagebox.showerror("错误", "程序已经在运行中!")
- sys.exit(1)
- def create_operation_mode_frame(self):
- frame = ttk.LabelFrame(self.root, text="操作模式", padding="10")
- frame.pack(fill="x", padx=10, pady=5)
- ttk.Radiobutton(frame, text="重启", variable=self.operation_mode, value="重启").pack(side="left", padx=3)
- ttk.Radiobutton(frame, text="关机", variable=self.operation_mode, value="关机").pack(side="left", padx=3)
- ttk.Radiobutton(frame, text="休眠", variable=self.operation_mode, value="休眠").pack(side="left", padx=3)
- def create_date_frame(self):
- frame = ttk.LabelFrame(self.root, text="日期设置", padding="10")
- frame.pack(fill="x", padx=10, pady=5)
- ttk.Label(frame, text="每月几号: ").grid(row=0, column=0, padx=5, pady=5)
- day_spinbox = ttk.Spinbox(frame, from_=1, to=31, textvariable=self.day_of_month, width=5)
- day_spinbox.grid(row=0, column=1, padx=5, pady=5)
- ttk.Label(frame, text="(设置每月的操作日期,自动处理月份天数差异)").grid(row=0, column=2, padx=5, pady=5, sticky="w")
- # 创建一个水平框架来放置星期选择,不使用网格布局
- ttk.Label(frame, text="每周几: ").grid(row=1, column=0, padx=5, pady=2, sticky="w")
-
- days_frame = ttk.Frame(frame)
- days_frame.grid(row=1, column=1, columnspan=6, sticky="w")
-
- days_order = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
-
- # 所有星期放在同一行显示
- for day in days_order:
- # 使用pack布局,水平排列
- ttk.Checkbutton(days_frame, text=day, variable=self.week_days[day]).pack(side="left", padx=3, pady=2)
- def create_time_frame(self):
- frame = ttk.LabelFrame(self.root, text="操作时间设置 (24小时制)", padding="10")
- frame.pack(fill="x", padx=10, pady=5)
- ttk.Label(frame, text="小时: ").grid(row=0, column=0, padx=3, pady=3)
- hour_spinbox = ttk.Spinbox(frame, from_=0, to=23, textvariable=self.hour, width=4)
- hour_spinbox.grid(row=0, column=1, padx=3, pady=3)
- ttk.Label(frame, text="分钟: ").grid(row=0, column=2, padx=3, pady=3)
- minute_spinbox = ttk.Spinbox(frame, from_=0, to=59, textvariable=self.minute, width=4)
- minute_spinbox.grid(row=0, column=3, padx=3, pady=3)
- def create_repeat_frame(self):
- frame = ttk.LabelFrame(self.root, text="重复频率", padding="10")
- frame.pack(fill="x", padx=10, pady=5)
- ttk.Radiobutton(frame, text="每日", variable=self.repeat_type, value="每日").pack(side="left", padx=3)
- ttk.Radiobutton(frame, text="每周", variable=self.repeat_type, value="每周").pack(side="left", padx=3)
- ttk.Radiobutton(frame, text="每月", variable=self.repeat_type, value="每月").pack(side="left", padx=3)
- def create_countdown_frame(self):
- frame = ttk.LabelFrame(self.root, text="倒计时操作", padding="10")
- frame.pack(fill="x", padx=10, pady=5)
- minute_spinbox = ttk.Spinbox(frame, from_=1, to=1440, textvariable=self.countdown_minutes, width=4)
- minute_spinbox.grid(row=0, column=0, padx=3, pady=3)
- ttk.Label(frame, text="后执行操作").grid(row=0, column=1, padx=3, pady=3)
- self.countdown_label = ttk.Label(frame, textvariable=self.countdown_status, foreground="blue", width=10)
- self.countdown_label.grid(row=0, column=2, padx=3, pady=3)
- ttk.Button(frame, text="开始", command=self.start_countdown, width=6).grid(row=0, column=3, padx=2, pady=3)
- ttk.Button(frame, text="取消", command=self.cancel_countdown, width=6).grid(row=0, column=4, padx=2, pady=3)
- def create_startup_frame(self):
- frame = ttk.LabelFrame(self.root, text="开机设置", padding="10")
- frame.pack(fill="x", padx=10, pady=5)
- self.startup_status_label = ttk.Label(frame, text="未开启", foreground="red", width=6)
- ttk.Checkbutton(frame, text="程序开机自启动: ", variable=self.startup_auto, command=self.toggle_startup).grid(row=0, column=0, padx=3, pady=3, sticky="w")
- self.startup_status_label.grid(row=0, column=1, padx=2, pady=3)
- self.startup_check_var = tk.BooleanVar(value=False)
- ttk.Checkbutton(frame, text="开机后自动运行: ", variable=self.startup_check_var).grid(row=1, column=0, padx=3, pady=3, sticky="w")
- ttk.Label(frame, textvariable=self.startup_program, width=15).grid(row=1, column=1, padx=2, pady=3, sticky="w")
- ttk.Button(frame, text="浏览...", command=self.browse_program, width=6).grid(row=1, column=2, padx=2, pady=3)
- def create_task_buttons(self):
- frame = ttk.Frame(self.root, padding="10")
- frame.pack(fill="x", padx=10, pady=5)
- ttk.Button(frame, text="设置定时任务", command=self.set_timed_task, width=10).pack(side="left", padx=5, pady=3)
- ttk.Button(frame, text="取消定时任务", command=self.cancel_timed_task, width=10).pack(side="right", padx=5, pady=3)
- def create_status_label(self):
- frame = ttk.LabelFrame(self.root, text="系统状态", padding="10")
- frame.pack(fill="x", padx=10, pady=5)
- # 添加当前时间显示
- self.current_time_var = tk.StringVar()
- ttk.Label(frame, textvariable=self.current_time_var, foreground="gray").grid(row=0, column=0, padx=3, pady=1, sticky="w")
- # 任务状态显示
- status_frame = ttk.Frame(frame)
- status_frame.grid(row=1, column=0, columnspan=3, sticky="ew", pady=2)
- for i, mode in enumerate(["重启", "关机", "休眠"]):
- ttk.Label(status_frame, textvariable=self.task_statuses[mode], foreground="blue").grid(row=0, column=i, padx=5, pady=1, sticky="w")
- # 程序状态显示
- self.program_status_var = tk.StringVar(value="程序运行正常")
- ttk.Label(frame, textvariable=self.program_status_var, foreground="green").grid(row=2, column=0, padx=3, pady=1, sticky="w")
- # 更新时间的函数
- def update_time():
- current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
- self.current_time_var.set(f"当前时间: {current_time}")
- # 每秒更新一次
- self.root.after(1000, update_time)
- # 启动时间更新
- update_time()
- def check_startup_status(self):
- try:
- key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Run', 0, winreg.KEY_READ)
- value, _ = winreg.QueryValueEx(key, 'TimedTaskTool')
- winreg.CloseKey(key)
- self.startup_auto.set(True)
- self.startup_status_label.config(text="已开启", foreground="green")
- except:
- self.startup_auto.set(False)
- self.startup_status_label.config(text="未开启", foreground="red")
- def toggle_startup(self):
- try:
- if self.startup_auto.get():
- # 开启开机自启动
- key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Run', 0, winreg.KEY_WRITE)
- winreg.SetValueEx(key, 'TimedTaskTool', 0, winreg.REG_SZ, sys.executable)
- winreg.CloseKey(key)
- self.startup_status_label.config(text="已开启", foreground="green")
- else:
- # 关闭开机自启动
- try:
- key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Run', 0, winreg.KEY_WRITE)
- winreg.DeleteValue(key, 'TimedTaskTool')
- winreg.CloseKey(key)
- self.startup_status_label.config(text="未开启", foreground="red")
- except FileNotFoundError:
- # 如果值不存在,依旧显示未开启
- self.startup_status_label.config(text="未开启", foreground="red")
- except Exception as e:
- messagebox.showerror("错误", f"设置开机自启动失败: {str(e)}")
- def browse_program(self):
- filename = filedialog.askopenfilename(title="选择程序", filetypes=[("所有文件", "*.*")])
- if filename:
- self.startup_program.set(filename)
- def start_countdown(self):
- minutes = self.countdown_minutes.get()
- seconds = minutes * 60
- self.countdown_status.set(f"倒计时中 ({minutes}分钟)")
- self.countdown_label.config(foreground="blue")
- self.root.update()
- # 创建倒计时线程
- def countdown():
- for i in range(seconds, 0, -1):
- minutes_left = i // 60
- seconds_left = i % 60
-
- # 最后5秒变红
- if i <= 5:
- self.countdown_label.config(foreground="red")
- self.countdown_status.set(f"即将执行 ({seconds_left}秒)")
- else:
- self.countdown_status.set(f"倒计时中 ({minutes_left}分{seconds_left}秒)")
-
- time.sleep(1)
- if self.countdown_status.get().startswith("未启动"):
- break
-
- if not self.countdown_status.get().startswith("未启动"):
- self.countdown_status.set("执行中...")
- self.execute_operation()
- self.countdown_label.config(foreground="blue")
- self.countdown_status.set("未启动")
-
- threading.Thread(target=countdown, daemon=True).start()
- def cancel_countdown(self):
- self.countdown_status.set("未启动")
- self.countdown_label.config(foreground="blue")
- def set_timed_task(self):
- # 获取设置
- operation = self.operation_mode.get()
- hour = self.hour.get()
- minute = self.minute.get()
- repeat_type = self.repeat_type.get()
- # 验证设置
- if repeat_type == "每周":
- selected_days = [day for day, var in self.week_days.items() if var.get()]
- if not selected_days:
- messagebox.showerror("错误", "请选择每周的至少一天")
- return
- # 取消该操作模式的现有任务
- self.cancel_timed_task(operation)
- # 创建新任务
- self.tasks[operation] = {"hour": hour, "minute": minute, "repeat_type": repeat_type}
- self.task_threads[operation] = threading.Thread(
- target=self.task_runner,
- args=(operation, hour, minute, repeat_type),
- daemon=True
- )
- self.task_threads[operation].start()
- # 更新状态
- if repeat_type == "每日":
- status = f"每日 {hour:02d}:{minute:02d} 执行"
- elif repeat_type == "每周":
- selected_days = [day for day, var in self.week_days.items() if var.get()]
- days_str = ", ".join(selected_days)
- status = f"每周 {days_str} {hour:02d}:{minute:02d} 执行"
- else: # 每月
- status = f"每月 {self.day_of_month.get()}号 {hour:02d}:{minute:02d} 执行"
- self.task_statuses[operation].set(f"{operation}任务: 已设置 ({status})")
- messagebox.showinfo("成功", f"{operation}定时任务已设置:\n{status}")
- def cancel_timed_task(self, operation=None):
- if operation:
- # 取消特定操作的任务
- if self.task_threads[operation] and self.task_threads[operation].is_alive():
- # 通知线程结束
- self.tasks[operation] = None
- self.task_threads[operation].join(1) # 等待线程结束,最多等待1秒
- self.task_threads[operation] = None
- self.task_statuses[operation].set(f"{operation}任务: 未设置")
- else:
- # 取消所有任务
- for mode in ["重启", "关机", "休眠"]:
- self.cancel_timed_task(mode)
- def task_runner(self, operation, hour, minute, repeat_type):
- while True:
- # 检查任务是否已被取消
- if self.tasks[operation] is None:
- break
- try:
- now = datetime.now()
- target_time = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
- # 如果今天的目标时间已过,则设置为明天
- if target_time <= now:
- target_time += timedelta(days=1)
- # 计算等待时间
- wait_time = (target_time - now).total_seconds()
- # 等待到目标时间,每秒检查一次任务是否已取消
- wait_done = True
- for _ in range(int(wait_time // 1)):
- if self.tasks[operation] is None:
- wait_done = False
- break
- time.sleep(1)
- # 检查是否需要执行
- if wait_done and self.tasks[operation] is not None:
- # 更新当前时间
- now = datetime.now()
-
- # 根据重复类型检查是否应该执行
- should_execute = False
- if repeat_type == "每日":
- should_execute = True
- elif repeat_type == "每周":
- weekday = now.weekday() # 0=周一, 6=周日
- weekdays_map = {"周一": 0, "周二": 1, "周三": 2, "周四": 3, "周五": 4, "周六": 5, "周日": 6}
- selected_days = [weekdays_map[day] for day, var in self.week_days.items() if var.get()]
- should_execute = weekday in selected_days
- elif repeat_type == "每月":
- # 处理月份天数差异
- day_of_month = self.day_of_month.get()
- last_day = (now.replace(day=28) + timedelta(days=4)).replace(day=1) - timedelta(days=1)
- if day_of_month > last_day.day:
- should_execute = now.day == last_day.day
- else:
- should_execute = now.day == day_of_month
- if should_execute:
- self.execute_operation(operation)
- except Exception as e:
- # 记录错误但不中断任务
- error_msg = f"任务执行错误: {str(e)}操作: {operation}"
- print(error_msg)
- # 可以选择记录到日志文件
- try:
- with open("task_error.log", "a") as f:
- f.write(f"{datetime.now()}: {error_msg}\n")
- except:
- pass
- # 短暂暂停后继续
- time.sleep(5)
- def execute_operation(self, operation=None):
- if operation is None:
- operation = self.operation_mode.get()
- try:
- if operation == "重启":
- os.system("shutdown /r /t 0")
- elif operation == "关机":
- os.system("shutdown /s /t 0")
- elif operation == "休眠":
- # 注意:Windows休眠命令可能需要管理员权限
- os.system("rundll32.exe powrprof.dll,SetSuspendState 0,1,0")
- except Exception as e:
- messagebox.showerror("操作失败", f"无法执行{operation}操作: {str(e)}")
- # 记录错误到日志
- try:
- with open("operation_error.log", "a") as f:
- f.write(f"{datetime.now()}: 无法执行{operation}操作: {str(e)}\n")
- except:
- pass
- def on_closing(root, app):
- # 取消所有定时任务
- app.cancel_timed_task()
- # 释放文件锁
- try:
- msvcrt.locking(app.lock.fileno(), msvcrt.LK_UNLCK, 1)
- app.lock.close()
- os.remove(app.lock_file)
- except:
- pass
- root.destroy()
- if __name__ == "__main__":
- root = tk.Tk()
- app = TimedTaskTool(root)
- root.protocol("WM_DELETE_WINDOW", lambda: on_closing(root, app))
- root.mainloop()
复制代码 |