長時間運算、批次列印、影片播放、簡報模式等場景下,應用程式需**暫時阻止螢幕保護程式啟動**,避免系統進入閒置狀態導致畫面鎖定、網路中斷、硬碟休眠影響任務連續性。Windows 在啟動螢幕保護程式前,會向前景視窗所屬執行緒發送 WM_SYSCOMMAND、wParam = SC_SCREENSAVE 訊息。攔截此訊息並標記已處理,即可取消啟動。本文以 C++ Builder (VCL) 為例,示範透過 TApplication::OnMessage 事件攔截系統命令的完整實作。
WM_SYSCOMMAND,wParam = SC_SCREENSAVE。OnMessage (或 WndProc) 中處理此訊息並將 Handled = true,系統**取消**螢幕保護啟動並重置閒置計時器。Handled = false),系統繼續預設流程啟動螢幕保護程式。Unit1.h)
#ifndef Unit1H
#define Unit1H
#include
#include
#include
#include
class TForm1 : public TForm
{
__published: // IDE 管理元件
private: // 使用者宣告
void __fastcall AppMessageHandler(tagMSG &Msg, bool &Handled);
public: // 使用者宣告
__fastcall TForm1(TComponent* Owner);
};
extern PACKAGE TForm1 *Form1;
#endif
Unit1.cpp)
#include
#pragma hdrstop
#include "Unit1.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TForm1::AppMessageHandler(tagMSG &Msg, bool &Handled)
{
// 攔截系統命令:螢幕保護啟動
if (Msg.message == WM_SYSCOMMAND && Msg.wParam == SC_SCREENSAVE) {
Handled = true; // 標記已處理,取消螢幕保護啟動
// 可選:記錄日誌、重置業務閒置計時器
}
else {
Handled = false; // 交由預設訊息處理流程
}
}
void __fastcall TForm1::FormCreate(TObject *Sender)
{
// 註冊應用程式層級訊息攔截器
Application->OnMessage = AppMessageHandler;
}
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
// 釋放時解除註冊 (良好習慣)
Application->OnMessage = nullptr;
}
// tagMSG 對應 Windows MSG 結構
typedef struct tagMSG {
HWND hwnd; // 視窗控制代碼
UINT message; // 訊息代碼 (WM_SYSCOMMAND = 0x0112)
WPARAM wParam; // 參數 (SC_SCREENSAVE = 0xF140)
LPARAM lParam; // 參數
DWORD time; // 訊息時間
POINT pt; // 滑鼠座標
} MSG, *PMSG, *LPMSG;
// TMessageEvent 委派型別
typedef void __fastcall (__closure *TMessageEvent)(tagMSG &Msg, bool &Handled);
__property TMessageEvent OnMessage = {read=FOnMessage, write=FOnMessage};
| 方法 | 適用範圍 | 優點 | 缺點 |
|---|---|---|---|
Application->OnMessage 攔截 SC_SCREENSAVE | VCL 應用程式全域 | 集中管理、不需修改每個視窗、可動態啟用/停用 | 僅對當前執行緒訊息佇列有效、需視窗焦點 |
SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED) | 執行緒級、無視窗也有效 | 現代 API、不依賴訊息迴圈、支援睡眠/顯示器分別控制 | 需 Vista+/Win7+、需記得呼叫 SetThreadExecutionState(ES_CONTINUOUS) 還原 |
視窗 WndProc 處理 WM_SYSCOMMAND | 單一視窗 | 精確控制特定視窗 | 多視窗需重複、易遺漏 |
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, FALSE, ...) | 全系統 (影響所有使用者) | 徹底關閉 | 需管理員權限、影響範圍過大、不建議應用程式使用 |
#include
// 啟動長時間任務前
void DisableSleepAndScreenSaver() {
// ES_CONTINUOUS: 通知系統此狀態持續有效,直到明確撤銷
// ES_DISPLAY_REQUIRED: 強制顯示器保持開啟
// ES_SYSTEM_REQUIRED: 強制系統保持運作 (防止睡眠)
SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED);
}
// 任務結束後
void RestorePowerPolicy() {
SetThreadExecutionState(ES_CONTINUOUS); // 撤銷先前設定,還原系統電源管理
}
優勢: 不需視窗焦點、主控台應用程式/服務亦可用、官方推薦 API、細粒度控制 (僅阻擋睡眠/或僅阻擋螢幕關閉)。
SetThreadExecutionState,測試無視窗主控台程式同樣有效。| 現象 | 可能原因 | 解決 |
|---|---|---|
| 螢幕保護仍啟動 | 訊息未送達前景執行緒、應用程式非前景、Handled 未設 true | 確認 Application->OnMessage 已註冊、視窗有焦點、Msg.wParam == SC_SCREENSAVE 比對正確 |
| 系統進入睡眠 | 僅阻擋螢幕保護,未阻擋睡眠 | 改用 SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED) |
| 多執行緒應用程式失效 | OnMessage 僅攔截主執行緒訊息佇列 | 改用 SetThreadExecutionState (執行緒級) 或各執行緒各自攔截 |
| 關閉程式後螢幕保護異常 | 未還原 SetThreadExecutionState | 確保 FormDestroy 或 atexit 呼叫還原函式 |
阻止螢幕保護程式啟動是長時間任務應用程式的基本禮貌。C++ Builder 透過 TApplication::OnMessage 攔截 WM_SYSCOMMAND/SC_SCREENSAVE 為經典 VCL 做法;現代開發**強烈建議改用 SetThreadExecutionState**,跨平台 (Win32/Win64)、無視窗依賴、官方維護、粒度更細。兩種方案皆需注意**成對啟用/還原**,避免永久干擾系統電源管理。選擇適合專案架構的方案,並納入自動化測試驗證,確保使用者體驗不受干擾。