import tkinter as tk
from tkinter import messagebox, simpledialog
import schedule
import time
import subprocess
import os
import threading
from datetime import datetime
import sys
import pystray
from PIL import Image
import logging
class TimerApp:
"""
定时任务管理器应用程序
功能:
1. 设置两个定时任务,在指定时间自动运行程序
2. 支持立即运行程序
3. 保存和加载配置文件
4. 日志记录
5. 系统托盘图标支持
使用方法:
app = TimerApp()
app.run()
"""
def __init__(self, config_file="timer_config.txt", log_file="timer_log.txt"):
"""
初始化定时任务应用
参数:
config_file -- 配置文件路径
log_file -- 日志文件路径
"""
self.config_file = config_file
self.log_file = log_file
self.scheduler_thread = None
self.scheduler_running = False
self.icon = None
# 设置日志
logging.basicConfig(
filename=self.log_file,
level=logging.INFO,
format='[%(asctime)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# 加载配置
self.load_config()
# 创建主窗口
self.root = tk.Tk()
self.root.title("沈阳地铁自控中心自启动管理器")
self.root.geometry("400x300")
# 设置图标
self.set_icon()
# 创建UI组件
self.create_widgets()
# 设置托盘图标
self.setup_tray_icon()
# 设置关闭窗口时的行为
self.root.protocol('WM_DELETE_WINDOW', self.hide_window)
def set_icon(self):
"""尝试设置应用图标"""
try:
# 尝试获取资源路径(适用于PyInstaller打包)
base_path = sys._MEIPASS if hasattr(sys, '_MEIPASS') else os.path.abspath(".")
icon_path = os.path.join(base_path, 'icon.ico')
if os.path.exists(icon_path):
self.root.iconbitmap(icon_path)
else:
# 如果找不到图标文件,尝试使用默认图标
self.root.iconbitmap(default='icon.ico')
except Exception as e:
logging.warning(f"无法设置图标: {str(e)}")
def load_config(self):
"""从配置文件加载设置"""
self.exe_path = ""
self.time1 = "04:20"
self.time2 = "07:00"
if os.path.exists(self.config_file):
try:
with open(self.config_file, 'r') as f:
lines = f.readlines()
if len(lines) >= 3:
self.exe_path = lines[0].strip()
self.time1 = lines[1].strip()
self.time2 = lines[2].strip()
logging.info(f"加载配置: {self.exe_path}, {self.time1}, {self.time2}")
except Exception as e:
logging.error(f"加载配置失败: {str(e)}")
messagebox.showerror("错误", f"配置文件加载失败: {str(e)}")
def save_config(self):
"""保存配置到文件"""
try:
with open(self.config_file, 'w') as f:
f.write(f"{self.exe_path}\n")
f.write(f"{self.time1}\n")
f.write(f"{self.time2}\n")
logging.info("配置已保存")
except Exception as e:
logging.error(f"保存配置失败: {str(e)}")
messagebox.showerror("错误", f"无法保存配置: {str(e)}")
def create_widgets(self):
"""创建应用界面组件"""
# 程序路径标签和按钮
tk.Label(self.root, text="目标程序路径:").pack(pady=(20, 5))
self.path_label = tk.Label(self.root, text=self.exe_path, wraplength=350)
self.path_label.pack()
tk.Button(self.root, text="更改程序路径", command=self.change_exe_path).pack(pady=5)
# 时间设置框架
time_frame = tk.Frame(self.root)
time_frame.pack(pady=20)
# 时间点1
tk.Label(time_frame, text="时间点1:").grid(row=0, column=0, padx=5, pady=5, sticky="e")
self.time1_var = tk.StringVar(value=self.time1)
self.time1_entry = tk.Entry(time_frame, textvariable=self.time1_var, width=8)
self.time1_entry.grid(row=0, column=1, padx=5, pady=5)
tk.Button(time_frame, text="修改", command=lambda: self.change_time(1)).grid(row=0, column=2, padx=5)
# 时间点2
tk.Label(time_frame, text="时间点2:").grid(row=1, column=0, padx=5, pady=5, sticky="e")
self.time2_var = tk.StringVar(value=self.time2)
self.time2_entry = tk.Entry(time_frame, textvariable=self.time2_var, width=8)
self.time2_entry.grid(row=1, column=1, padx=5, pady=5)
tk.Button(time_frame, text="修改", command=lambda: self.change_time(2)).grid(row=1, column=2, padx=5)
# 状态标签
self.status_var = tk.StringVar(value="状态: 等待中...")
tk.Label(self.root, textvariable=self.status_var, fg="blue").pack(pady=10)
# 操作按钮
tk.Button(self.root, text="立即运行程序", command=self.run_now).pack(pady=5)
tk.Button(self.root, text="退出程序", command=self.quit_app).pack(pady=5)
def change_time(self, time_num):
"""修改时间点"""
new_time = simpledialog.askstring(
"修改时间",
f"请输入时间点{time_num} (HH:MM):",
initialvalue=self.time1 if time_num == 1 else self.time2
)
if new_time:
try:
# 验证时间格式
datetime.strptime(new_time, "%H:%M")
if time_num == 1:
self.time1 = new_time
self.time1_var.set(new_time)
else:
self.time2 = new_time
self.time2_var.set(new_time)
# 保存配置
self.save_config()
# 重启调度器以应用新时间
self.restart_scheduler()
messagebox.showinfo("成功", f"时间点{time_num}已更新为: {new_time}")
logging.info(f"时间点{time_num}更新为: {new_time}")
except ValueError:
messagebox.showerror("错误", "无效的时间格式! 请使用 HH:MM 格式")
logging.warning(f"无效的时间格式: {new_time}")
def change_exe_path(self):
"""修改程序路径"""
new_path = simpledialog.askstring(
"修改程序路径",
"请输入新的程序路径:",
initialvalue=self.exe_path
)
if new_path:
if os.path.exists(new_path) and new_path.lower().endswith('.exe'):
self.exe_path = new_path
self.path_label.config(text=new_path)
self.save_config()
messagebox.showinfo("成功", f"程序路径已更新为: {new_path}")
logging.info(f"程序路径更新为: {new_path}")
else:
messagebox.showerror("错误", "无效的路径或文件不是exe程序")
logging.warning(f"无效的程序路径: {new_path}")
def run_exe(self):
"""运行目标程序"""
try:
if not self.exe_path:
self.status_var.set("状态: 错误 - 未设置程序路径")
logging.error("未设置程序路径")
self.root.after(3000, lambda: self.status_var.set("状态: 等待中..."))
return
if os.path.exists(self.exe_path):
current_time = datetime.now().strftime('%H:%M:%S')
self.status_var.set(f"状态: {current_time} 正在运行程序")
logging.info(f"运行程序: {self.exe_path}")
# 在新窗口中启动程序
subprocess.Popen(f'start "" "{self.exe_path}"', shell=True)
# 3秒后恢复状态
self.root.after(3000, lambda: self.status_var.set("状态: 等待中..."))
else:
self.status_var.set("状态: 错误 - 程序文件不存在")
logging.error(f"程序文件不存在: {self.exe_path}")
self.root.after(3000, lambda: self.status_var.set("状态: 等待中..."))
except Exception as e:
self.status_var.set(f"状态: 错误 - {str(e)}")
logging.error(f"运行程序失败: {str(e)}")
self.root.after(3000, lambda: self.status_var.set("状态: 等待中..."))
def run_now(self):
"""立即运行程序"""
threading.Thread(target=self.run_exe, daemon=True).start()
logging.info("用户手动触发程序运行")
def scheduler_loop(self):
"""调度器主循环"""
logging.info("调度器启动")
while self.scheduler_running:
try:
schedule.run_pending()
time.sleep(1)
except Exception as e:
logging.error(f"调度器错误: {str(e)}")
time.sleep(5)
def setup_schedule(self):
"""设置定时任务"""
schedule.clear()
if self.time1:
schedule.every().day.at(self.time1).do(self.run_exe)
if self.time2:
schedule.every().day.at(self.time2).do(self.run_exe)
status = f"已设置定时任务 - {self.time1} 和 {self.time2}"
self.status_var.set(f"状态: {status}")
logging.info(status)
def start_scheduler(self):
"""启动调度器"""
if not self.scheduler_running:
self.scheduler_running = True
self.setup_schedule()
self.scheduler_thread = threading.Thread(target=self.scheduler_loop, daemon=True)
self.scheduler_thread.start()
def restart_scheduler(self):
"""重启调度器"""
if self.scheduler_running:
self.scheduler_running = False
if self.scheduler_thread and self.scheduler_thread.is_alive():
self.scheduler_thread.join(timeout=1.0)
self.start_scheduler()
def setup_tray_icon(self):
"""设置系统托盘图标"""
def on_quit():
self.quit_app()
def show_window():
self.root.after(0, self.root.deiconify)
if self.icon:
self.icon.stop()
def create_icon():
"""创建托盘图标"""
try:
# 尝试加载图标
base_path = sys._MEIPASS if hasattr(sys, '_MEIPASS') else os.path.abspath(".")
icon_path = os.path.join(base_path, 'icon.ico')
if not os.path.exists(icon_path):
# 创建空白图标作为后备
image = Image.new('RGB', (64, 64), (70, 130, 180))
else:
image = Image.open(icon_path)
menu = (
pystray.MenuItem('显示窗口', show_window),
pystray.MenuItem('退出', on_quit)
)
self.icon = pystray.Icon("TimerApp", image, "定时任务管理器", menu)
self.icon.run()
except Exception as e:
logging.error(f"创建托盘图标失败: {str(e)}")
# 在单独的线程中运行托盘图标
threading.Thread(target=create_icon, daemon=True).start()
def hide_window(self):
"""隐藏主窗口"""
self.root.withdraw()
def quit_app(self):
"""退出应用程序"""
self.scheduler_running = False
logging.info("应用程序退出")
if self.icon:
self.icon.stop()
self.root.quit()
self.root.destroy()
os._exit(0)
def run(self):
"""运行应用程序"""
# 启动调度器
self.start_scheduler()
# 启动主循环
self.root.mainloop()
if __name__ == "__main__":
app = TimerApp()
app.run()
增加可添加时间点功能,最多可添加5个,保持原代码不变
最新发布