基于PyQt5實現(xiàn)的Windows定時關(guān)機工具
概述
在日常使用電腦的過程中,我們經(jīng)常會遇到需要定時關(guān)機的場景,比如:
- 夜間下載文件,想讓電腦在任務(wù)完成后自動關(guān)機。
- 長時間運行的程序,需要在某個時間點關(guān)閉系統(tǒng)。
- 限制電腦使用時間,避免長時間占用資源。
雖然 Windows 自帶 shutdown
命令可以定時關(guān)機,但操作方式較為繁瑣,缺乏可視化界面。因此,本篇文章將帶大家實現(xiàn)一個基于 PyQt5 的 Windows 定時關(guān)機工具,支持定時或延時關(guān)機、重啟、注銷,并提供系統(tǒng)托盤功能,方便隨時管理關(guān)機任務(wù)。
功能介紹
本工具主要具備以下功能:
定時關(guān)機 —— 設(shè)定具體時間,到點自動關(guān)機。
延時關(guān)機 —— 設(shè)置倒計時,倒計時結(jié)束后自動關(guān)機。
重啟 & 注銷 —— 除關(guān)機外,還可執(zhí)行系統(tǒng)重啟和注銷操作。
取消操作 —— 關(guān)機前可隨時取消,避免誤操作。
系統(tǒng)托盤支持 —— 運行后最小化到系統(tǒng)托盤,不影響日常操作。
人性化提示 —— 關(guān)機前彈出提醒,避免突發(fā)關(guān)機。
代碼實現(xiàn)
1. 安裝依賴
在運行代碼之前,我們需要先安裝 PyQt5 庫:
pip install PyQt5 pyqt5-tools
2. 代碼編寫
以下是完整的代碼實現(xiàn):
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, QTime class ShutdownApp(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setWindowTitle('Windows 定時關(guān)機工具') self.setGeometry(600, 300, 300, 200) self.label = QLabel('請選擇關(guān)機時間:', self) self.timeEdit = QTimeEdit(self) self.timeEdit.setDisplayFormat("HH:mm") self.startButton = QPushButton('設(shè)置關(guān)機', self) self.startButton.clicked.connect(self.scheduleShutdown) self.cancelButton = QPushButton('取消關(guān)機', 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("請選擇一個未來的時間!") return self.label.setText(f"關(guān)機已設(shè)置,將在 {shutdown_time.toString()} 執(zhí)行") os.system(f'shutdown -s -t {seconds}') def cancelShutdown(self): os.system('shutdown -a') self.label.setText("關(guān)機已取消!") if __name__ == '__main__': app = QApplication(sys.argv) ex = ShutdownApp() ex.show() sys.exit(app.exec_())
功能使用
1. 運行軟件
python shutdown_tool.py
2. 設(shè)置定時關(guān)機
- 選擇時間
- 點擊 “設(shè)置關(guān)機”
- 程序?qū)⒂嬎闶S鄷r間,并執(zhí)行關(guān)機命令
3. 取消關(guān)機
- 如果想取消定時關(guān)機,點擊 “取消關(guān)機” 按鈕
- 也可以手動在命令行執(zhí)行:
shutdown -a
4. 系統(tǒng)托盤
- 運行后可最小化到托盤
- 右鍵點擊托盤圖標可 退出應(yīng)用
技術(shù)要點解析
關(guān)機命令
Windows 提供 shutdown
命令來執(zhí)行關(guān)機任務(wù):
- 定時關(guān)機:
shutdown -s -t 秒數(shù)
- 取消關(guān)機:
shutdown -a
計算關(guān)機時間
我們使用 QTime
計算當前時間到設(shè)定時間的 秒數(shù),避免時間計算錯誤:
seconds = current_time.secsTo(shutdown_time)
托盤圖標支持
使用 QSystemTrayIcon
實現(xiàn)最小化到托盤:
self.trayIcon = QSystemTrayIcon(QIcon("icon.png"), self)
這樣即使窗口關(guān)閉,應(yīng)用仍能在后臺運行。
運行效果
相關(guān)源碼
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, QColor def resource_path(relative_path): """ 獲取資源的絕對路徑,適用于開發(fā)環(huán)境和PyInstaller單文件模式 """ if hasattr(sys, '_MEIPASS'): # PyInstaller創(chuàng)建的臨時文件夾 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 = False self.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() # 系統(tǒng)托盤 self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setIcon(QIcon(resource_path("icon.ico"))) self.tray_icon.setToolTip("定時關(guān)機") 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): """設(shè)置全局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("定時關(guān)機") 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("定時關(guān)機") self.delay_time_radio = QRadioButton("延時關(guān)機") 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("關(guān)機") 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("隨系統(tǒng)啟動") self.loop_exec_check = QCheckBox("循環(huán)執(zhí)行") 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("保存設(shè)置") 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): return config = 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_USER sub_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, "定時關(guān)機", 0, win32con.REG_SZ, sys.executable) else: try: win32api.RegDeleteValue(reg_key, "定時關(guān)機") except: pass win32api.RegCloseKey(reg_key) except Exception as e: QMessageBox.warning(self, "警告", f"設(shè)置自啟動失敗: {str(e)}") def start_task(self): if self.task_running: return self.task_running = True self.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_time else: 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 - now if remaining.total_seconds() <= 0: self.execute_action() if self.loop_exec_check.isChecked(): self.start_task() else: self.cancel_task() return hours, 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"定時關(guān)機\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): """取消定時任務(wù)""" if not self.task_running: QMessageBox.warning(self, "警告", "沒有正在運行的任務(wù)") return reply = QMessageBox.question( self, "確認", "確定要取消定時任務(wù)嗎?", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: self.task_running = False self.countdown_timer.stop() self.remaining_time_label.setText("") self.tray_icon.setToolTip("定時關(guān)機") self.toggle_components(False) self.cancel_btn.setEnabled(False) # 顯示取消成功的提示 QMessageBox.information(self, "提示", "定時任務(wù)已取消", 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): """重寫關(guān)閉事件,改為最小化到托盤""" if self.isVisible(): self.hide() self.tray_icon.show() # 確保托盤圖標顯示 if not self.first_show: # 第一次啟動時不顯示消息 self.tray_icon.showMessage( "定時關(guān)機", "程序已最小化到托盤", QSystemTrayIcon.Information, 2000 ) self.first_show = False event.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("定時關(guān)機") if not shared.create(512, QSharedMemory.ReadWrite): QMessageBox.warning(None, "警告", "程序已經(jīng)在運行!") sys.exit(0) # 設(shè)置高DPI支持 QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) app = QApplication(sys.argv) window = ShutdownApp() # 如果配置了隨系統(tǒng)啟動且循環(huán)執(zhí)行,則不顯示窗口 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()
總結(jié)與優(yōu)化方向
優(yōu)點
界面簡潔,操作方便
系統(tǒng)托盤支持,后臺靜默運行
支持定時 & 倒計時模式,滿足不同需求
可優(yōu)化方向
支持多任務(wù)管理(同時設(shè)置多個定時任務(wù))
增加日志記錄(記錄每次關(guān)機任務(wù))
增加任務(wù)進度條(倒計時可視化顯示)
如果你對這個工具感興趣,可以嘗試優(yōu)化它,讓它變得更加智能!
以上就是基于PyQt5實現(xiàn)的Windows定時關(guān)機工具的詳細內(nèi)容,更多關(guān)于PyQt5 Windows定時關(guān)機的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python實現(xiàn)去除圖片中指定顏色的像素功能示例
這篇文章主要介紹了Python實現(xiàn)去除圖片中指定顏色的像素功能,結(jié)合具體實例形式分析了Python基于pil與cv2模塊的圖形載入、運算、轉(zhuǎn)換等相關(guān)操作技巧,需要的朋友可以參考下2019-04-04Python使用psutil庫實現(xiàn)系統(tǒng)監(jiān)控與管理詳解
在我們的測試工作中,監(jiān)控和管理系統(tǒng)資源是一項重要的任務(wù),本文將介紹如何使用psutil庫來實現(xiàn)系統(tǒng)監(jiān)控和管理,以及一些實用的技巧和示例,希望對大家有所幫助2022-10-10python把ipynb文件轉(zhuǎn)換成pdf文件過程詳解
這篇文章主要介紹了用python把ipynb文件轉(zhuǎn)換成pdf文件過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-07-07