🖥? 你的 Windows 關機助手——PyQt5 版定時關機工具
相關資源文件已經打包成EXE文件,可雙擊直接運行程序,且文章末尾已附上相關源碼,以供大家學習交流,博主主頁還有更多Python相關程序案例,秉著開源精神的想法,望大家喜歡,點個關注不迷路!!!
📌 概述
在日常使用電腦的過程中,我們經常會遇到需要定時關機的場景,比如:
- 夜間下載文件,想讓電腦在任務完成后自動關機。
- 長時間運行的程序,需要在某個時間點關閉系統。
- 限制電腦使用時間,避免長時間占用資源。
雖然 Windows 自帶 shutdown
命令可以定時關機,但操作方式較為繁瑣,缺乏可視化界面。因此,本篇文章將帶大家實現一個基于 PyQt5 的 Windows 定時關機工具,支持定時或延時關機、重啟、注銷,并提供系統托盤功能,方便隨時管理關機任務。
🎯 功能介紹
本工具主要具備以下功能:
? 定時關機 —— 設定具體時間,到點自動關機。
? 延時關機 —— 設置倒計時,倒計時結束后自動關機。
? 重啟 & 注銷 —— 除關機外,還可執行系統重啟和注銷操作。
? 取消操作 —— 關機前可隨時取消,避免誤操作。
? 系統托盤支持 —— 運行后最小化到系統托盤,不影響日常操作。
? 人性化提示 —— 關機前彈出提醒,避免突發關機。
🔧 代碼實現
📌 1. 安裝依賴
在運行代碼之前,我們需要先安裝 PyQt5 庫:
pip install PyQt5 pyqt5-tools
📌 2. 代碼編寫
以下是完整的代碼實現:
import sys
import os
import time
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout, QTimeEdit, QSystemTrayIcon, QMenu, QAction
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QTimer, QTimeclass ShutdownApp(QWidget):def __init__(self):super().__init__()self.initUI()def initUI(self):self.setWindowTitle('Windows 定時關機工具')self.setGeometry(600, 300, 300, 200)self.label = QLabel('請選擇關機時間:', self)self.timeEdit = QTimeEdit(self)self.timeEdit.setDisplayFormat("HH:mm")self.startButton = QPushButton('設置關機', self)self.startButton.clicked.connect(self.scheduleShutdown)self.cancelButton = QPushButton('取消關機', self)self.cancelButton.clicked.connect(self.cancelShutdown)layout = QVBoxLayout()layout.addWidget(self.label)layout.addWidget(self.timeEdit)layout.addWidget(self.startButton)layout.addWidget(self.cancelButton)self.setLayout(layout)# 托盤功能self.trayIcon = QSystemTrayIcon(QIcon("icon.png"), self)trayMenu = QMenu()exitAction = QAction("退出", self)exitAction.triggered.connect(self.close)trayMenu.addAction(exitAction)self.trayIcon.setContextMenu(trayMenu)self.trayIcon.show()def scheduleShutdown(self):shutdown_time = self.timeEdit.time()current_time = QTime.currentTime()seconds = current_time.secsTo(shutdown_time)if seconds <= 0:self.label.setText("請選擇一個未來的時間!")returnself.label.setText(f"關機已設置,將在 {shutdown_time.toString()} 執行")os.system(f'shutdown -s -t {seconds}')def cancelShutdown(self):os.system('shutdown -a')self.label.setText("關機已取消!")if __name__ == '__main__':app = QApplication(sys.argv)ex = ShutdownApp()ex.show()sys.exit(app.exec_())
📌 功能使用
🛠? 1. 運行軟件
python shutdown_tool.py
? 2. 設置定時關機
- 選擇時間
- 點擊 “設置關機”
- 程序將計算剩余時間,并執行關機命令
🚫 3. 取消關機
-
如果想取消定時關機,點擊 “取消關機” 按鈕
-
也可以手動在命令行執行:
shutdown -a
🔽 4. 系統托盤
- 運行后可最小化到托盤
- 右鍵點擊托盤圖標可 退出應用
📊 技術要點解析
1?? 關機命令
Windows 提供 shutdown
命令來執行關機任務:
-
定時關機:
shutdown -s -t 秒數
-
取消關機:
shutdown -a
2?? 計算關機時間
我們使用 QTime
計算當前時間到設定時間的 秒數,避免時間計算錯誤:
seconds = current_time.secsTo(shutdown_time)
3?? 托盤圖標支持
使用 QSystemTrayIcon
實現最小化到托盤:
self.trayIcon = QSystemTrayIcon(QIcon("icon.png"), self)
這樣即使窗口關閉,應用仍能在后臺運行。
運行效果
相關源碼
import os
import sys
import time
import configparser
import win32api
import win32con
from datetime import datetime, timedelta
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QRadioButton, QDateTimeEdit, QLabel, QPushButton, QCheckBox, QSystemTrayIcon, QMenu, QMessageBox, QSpacerItem, QSizePolicy, QFrame)
from PyQt5.QtCore import Qt, QTimer, QDateTime, QTime, QSize, QSharedMemory
from PyQt5.QtGui import QIcon, QFont, QPalette, QColordef resource_path(relative_path):""" 獲取資源的絕對路徑,適用于開發環境和PyInstaller單文件模式 """if hasattr(sys, '_MEIPASS'):# PyInstaller創建的臨時文件夾return os.path.join(sys._MEIPASS, relative_path)return os.path.join(os.path.abspath('.'), relative_path)class ShutdownApp(QMainWindow):def __init__(self):super().__init__()self.task_running = Falseself.config_file = os.path.join(os.getenv('APPDATA'), 'shutdown_config.ini')self.first_show = True # 用于跟蹤是否是第一次顯示self.setup_ui_style()self.init_ui()self.load_config()# 系統托盤self.tray_icon = QSystemTrayIcon(self)self.tray_icon.setIcon(QIcon(resource_path("icon.ico")))self.tray_icon.setToolTip("定時關機")self.tray_icon.activated.connect(self.tray_icon_activated)# 托盤菜單self.tray_menu = QMenu()self.show_action = self.tray_menu.addAction("顯示")self.exit_action = self.tray_menu.addAction("退出")self.show_action.triggered.connect(self.show_normal)self.exit_action.triggered.connect(self.confirm_exit)self.tray_icon.setContextMenu(self.tray_menu)self.tray_icon.show() # 確保托盤圖標顯示# 顯示當前時間self.timer = QTimer(self)self.timer.timeout.connect(self.update_current_time)self.timer.start(1000)# 剩余時間計時器self.countdown_timer = QTimer(self)self.countdown_timer.timeout.connect(self.update_remaining_time)def setup_ui_style(self):"""設置全局UI樣式"""self.setStyleSheet("""QMainWindow {background-color: #f5f5f5;}QGroupBox {border: 1px solid #ccc;border-radius: 4px;margin-top: 10px;padding-top: 15px;font-weight: bold;color: #333;}QGroupBox::title {subcontrol-origin: margin;left: 10px;padding: 0 3px;}QRadioButton, QCheckBox {color: #444;}QPushButton {background-color: #4CAF50;color: white;border: none;padding: 8px 16px;border-radius: 4px;min-width: 80px;}QPushButton:hover {background-color: #45a049;}QPushButton:disabled {background-color: #cccccc;}QPushButton#cancel_btn {background-color: #f44336;}QPushButton#cancel_btn:hover {background-color: #d32f2f;}QDateTimeEdit {padding: 5px;border: 1px solid #ddd;border-radius: 4px;}QLabel#current_time_label {font-size: 16px;color: #333;padding: 5px;background-color: #e9f5e9;border-radius: 4px;}QLabel#remaining_time_label {font-size: 16px;color: #d32f2f;font-weight: bold;padding: 5px;background-color: #f9e9e9;border-radius: 4px;}""")def init_ui(self):self.setWindowTitle("定時關機")self.setWindowIcon(QIcon(resource_path("icon.ico")))self.resize(300, 440)# 主窗口布局main_widget = QWidget()self.setCentralWidget(main_widget)layout = QVBoxLayout(main_widget)layout.setContentsMargins(12, 12, 12, 12)layout.setSpacing(10)# 當前時間顯示self.current_time_label = QLabel()self.current_time_label.setAlignment(Qt.AlignCenter)self.current_time_label.setObjectName("current_time_label")layout.addWidget(self.current_time_label)# 添加分隔線line = QFrame()line.setFrameShape(QFrame.HLine)line.setFrameShadow(QFrame.Sunken)layout.addWidget(line)# 定時/延時選擇time_type_group = QGroupBox("時間類型")time_type_layout = QHBoxLayout()time_type_layout.setContentsMargins(10, 15, 10, 10)self.fixed_time_radio = QRadioButton("定時關機")self.delay_time_radio = QRadioButton("延時關機")self.fixed_time_radio.setChecked(True)time_type_layout.addWidget(self.fixed_time_radio)time_type_layout.addWidget(self.delay_time_radio)time_type_group.setLayout(time_type_layout)layout.addWidget(time_type_group)# 定時時間選擇self.fixed_datetime = QDateTimeEdit()self.fixed_datetime.setDisplayFormat("yyyy-MM-dd HH:mm:ss")self.fixed_datetime.setDateTime(QDateTime.currentDateTime())self.fixed_datetime.setCalendarPopup(True)layout.addWidget(self.fixed_datetime)# 延時時間選擇self.delay_datetime = QDateTimeEdit()self.delay_datetime.setDisplayFormat("HH:mm:ss")self.delay_datetime.setTime(QTime(0, 0, 0))self.delay_datetime.setVisible(False)layout.addWidget(self.delay_datetime)# 連接信號self.fixed_time_radio.toggled.connect(self.on_time_type_changed)# 操作類型action_group = QGroupBox("操作類型")action_layout = QHBoxLayout()action_layout.setContentsMargins(10, 15, 10, 10)self.shutdown_radio = QRadioButton("關機")self.restart_radio = QRadioButton("重啟")self.logoff_radio = QRadioButton("注銷")self.shutdown_radio.setChecked(True)action_layout.addWidget(self.shutdown_radio)action_layout.addWidget(self.restart_radio)action_layout.addWidget(self.logoff_radio)action_group.setLayout(action_layout)layout.addWidget(action_group)# 選項options_group = QGroupBox("選項")options_layout = QVBoxLayout()options_layout.setContentsMargins(10, 15, 10, 10)self.auto_start_check = QCheckBox("隨系統啟動")self.loop_exec_check = QCheckBox("循環執行")options_layout.addWidget(self.auto_start_check)options_layout.addWidget(self.loop_exec_check)options_group.setLayout(options_layout)layout.addWidget(options_group)# 剩余時間顯示self.remaining_time_label = QLabel()self.remaining_time_label.setAlignment(Qt.AlignCenter)self.remaining_time_label.setObjectName("remaining_time_label")layout.addWidget(self.remaining_time_label)# 按鈕布局button_layout = QHBoxLayout()button_layout.setContentsMargins(0, 10, 0, 0)button_layout.setSpacing(15)self.save_btn = QPushButton("保存設置")self.cancel_btn = QPushButton("取消")self.cancel_btn.setObjectName("cancel_btn")self.cancel_btn.setEnabled(False)self.save_btn.clicked.connect(self.save_config)self.cancel_btn.clicked.connect(self.cancel_task)button_layout.addWidget(self.save_btn)button_layout.addWidget(self.cancel_btn)layout.addLayout(button_layout)# 添加彈簧使布局更緊湊layout.addSpacerItem(QSpacerItem(20, 10, QSizePolicy.Minimum, QSizePolicy.Expanding))def on_time_type_changed(self, checked):self.fixed_datetime.setVisible(checked)self.delay_datetime.setVisible(not checked)def update_current_time(self):current_time = datetime.now().strftime("%Y年%m月%d日 %H:%M:%S")self.current_time_label.setText(f"當前時間: {current_time}")def save_config(self):config = configparser.ConfigParser()config['task'] = {'time_type': 'fixed' if self.fixed_time_radio.isChecked() else 'delay','time': self.fixed_datetime.dateTime().toString("HHmmss") if self.fixed_time_radio.isChecked() else self.delay_datetime.time().toString("HHmmss"),'execute_type': 'shutdown' if self.shutdown_radio.isChecked() else 'restart' if self.restart_radio.isChecked() else 'logoff','auto_start': '1' if self.auto_start_check.isChecked() else '0','task_circ': '1' if self.loop_exec_check.isChecked() else '0'}with open(self.config_file, 'w') as f:config.write(f)self.set_auto_start(self.auto_start_check.isChecked())self.cancel_btn.setEnabled(True) # 保存后啟用取消按鈕self.start_task()def load_config(self):if not os.path.exists(self.config_file):returnconfig = configparser.ConfigParser()config.read(self.config_file)if 'task' in config:task_config = config['task']# 時間類型if task_config.get('time_type', 'fixed') == 'fixed':self.fixed_time_radio.setChecked(True)time_str = task_config.get('time', '000000')qtime = QTime.fromString(time_str, "HHmmss")current_date = QDateTime.currentDateTime()self.fixed_datetime.setDateTime(QDateTime(current_date.date(), qtime))else:self.delay_time_radio.setChecked(True)time_str = task_config.get('time', '000000')qtime = QTime.fromString(time_str, "HHmmss")self.delay_datetime.setTime(qtime)# 操作類型execute_type = task_config.get('execute_type', 'shutdown')if execute_type == 'shutdown':self.shutdown_radio.setChecked(True)elif execute_type == 'restart':self.restart_radio.setChecked(True)else:self.logoff_radio.setChecked(True)# 選項self.auto_start_check.setChecked(task_config.get('auto_start', '0') == '1')self.loop_exec_check.setChecked(task_config.get('task_circ', '0') == '1')if self.loop_exec_check.isChecked():self.cancel_btn.setEnabled(True)self.start_task()def set_auto_start(self, enable):key = win32con.HKEY_CURRENT_USERsub_key = r"Software\Microsoft\Windows\CurrentVersion\Run"try:reg_key = win32api.RegOpenKey(key, sub_key, 0, win32con.KEY_ALL_ACCESS)if enable:win32api.RegSetValueEx(reg_key, "定時關機", 0, win32con.REG_SZ, sys.executable)else:try:win32api.RegDeleteValue(reg_key, "定時關機")except:passwin32api.RegCloseKey(reg_key)except Exception as e:QMessageBox.warning(self, "警告", f"設置自啟動失敗: {str(e)}")def start_task(self):if self.task_running:returnself.task_running = Trueself.toggle_components(True)if self.fixed_time_radio.isChecked():target_time = self.fixed_datetime.dateTime().toPyDateTime()now = datetime.now()if target_time < now:target_time += timedelta(days=1)self.target_time = target_timeelse:delay = self.delay_datetime.time()self.target_time = datetime.now() + timedelta(hours=delay.hour(),minutes=delay.minute(),seconds=delay.second())self.countdown_timer.start(1000)self.update_remaining_time()def update_remaining_time(self):now = datetime.now()remaining = self.target_time - nowif remaining.total_seconds() <= 0:self.execute_action()if self.loop_exec_check.isChecked():self.start_task()else:self.cancel_task()returnhours, remainder = divmod(int(remaining.total_seconds()), 3600)minutes, seconds = divmod(remainder, 60)remaining_str = f"{hours}小時{minutes}分{seconds}秒"self.remaining_time_label.setText(f"剩余時間: {remaining_str}")# 更新托盤提示self.tray_icon.setToolTip(f"定時關機\n剩余時間: {remaining_str}")def execute_action(self):if self.shutdown_radio.isChecked():os.system("shutdown -s -t 0")elif self.restart_radio.isChecked():os.system("shutdown -r -t 0")else:os.system("shutdown -l")def cancel_task(self):"""取消定時任務"""if not self.task_running:QMessageBox.warning(self, "警告", "沒有正在運行的任務")returnreply = QMessageBox.question(self, "確認", "確定要取消定時任務嗎?", QMessageBox.Yes | QMessageBox.No)if reply == QMessageBox.Yes:self.task_running = Falseself.countdown_timer.stop()self.remaining_time_label.setText("")self.tray_icon.setToolTip("定時關機")self.toggle_components(False)self.cancel_btn.setEnabled(False)# 顯示取消成功的提示QMessageBox.information(self, "提示", "定時任務已取消", QMessageBox.Ok)def toggle_components(self, disabled):self.fixed_time_radio.setDisabled(disabled)self.delay_time_radio.setDisabled(disabled)self.fixed_datetime.setDisabled(disabled)self.delay_datetime.setDisabled(disabled)self.shutdown_radio.setDisabled(disabled)self.restart_radio.setDisabled(disabled)self.logoff_radio.setDisabled(disabled)self.auto_start_check.setDisabled(disabled)self.loop_exec_check.setDisabled(disabled)self.save_btn.setDisabled(disabled)def tray_icon_activated(self, reason):if reason == QSystemTrayIcon.DoubleClick:self.show_normal()def show_normal(self):self.show()self.setWindowState(self.windowState() & ~Qt.WindowMinimized)self.activateWindow()self.raise_()def closeEvent(self, event):"""重寫關閉事件,改為最小化到托盤"""if self.isVisible():self.hide()self.tray_icon.show() # 確保托盤圖標顯示if not self.first_show: # 第一次啟動時不顯示消息self.tray_icon.showMessage("定時關機", "程序已最小化到托盤", QSystemTrayIcon.Information, 2000)self.first_show = Falseevent.ignore()else:# 真正退出程序self.tray_icon.hide()event.accept()def confirm_exit(self):reply = QMessageBox.question(self, '確認', '是否退出?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)if reply == QMessageBox.Yes:self.tray_icon.hide()QApplication.quit()def main():# 防止重復運行shared = QSharedMemory("定時關機")if not shared.create(512, QSharedMemory.ReadWrite):QMessageBox.warning(None, "警告", "程序已經在運行!")sys.exit(0)# 設置高DPI支持QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)app = QApplication(sys.argv)window = ShutdownApp()# 如果配置了隨系統啟動且循環執行,則不顯示窗口config_file = os.path.join(os.getenv('APPDATA'), 'shutdown_config.ini')if os.path.exists(config_file):config = configparser.ConfigParser()config.read(config_file)if 'task' in config and config['task'].get('auto_start', '0') == '1':if config['task'].get('task_circ', '0') == '1':window.showMinimized()else:window.show()else:window.show()else:window.show()sys.exit(app.exec_())if __name__ == '__main__':main()
🎉 總結與優化方向
🔹 優點
? 界面簡潔,操作方便
? 系統托盤支持,后臺靜默運行
? 支持定時 & 倒計時模式,滿足不同需求
🔹 可優化方向
🚀 支持多任務管理(同時設置多個定時任務)
🚀 增加日志記錄(記錄每次關機任務)
🚀 增加任務進度條(倒計時可視化顯示)
如果你對這個工具感興趣,可以嘗試優化它,讓它變得更加智能!💡
你會如何改進這個工具?歡迎在評論區交流你的想法! 🚀
📥 資源下載
- 源碼以及exe文件直接下載 ZIP:🔗 點此下載
🚀 趕快下載體驗吧,如果覺得有幫助,歡迎 Star 支持!