許多常駐程式在系統托盤 (通知區域) 顯示圖示提供快速存取。當 Explorer.exe 異常終止並自動重啟時,托盤區域會重建,但應用程式若未感知此事件,原有圖示將永久消失,導致使用者無法互動、甚至誤以為程式已關閉。本文提供完整的 C++Builder (VCL) 解決方案,攔截 TaskbarCreated 訊息自動重建圖示。
Windows 在重建工作列時,會向所有頂層視窗廣播一個已註冊的自訂訊息:
UINT WM_TASKBARCREATED = RegisterWindowMessage(L"TaskbarCreated");
應用程式只需在視窗程序中攔截此訊息,呼叫重新加入托盤的邏輯即可。
Unit1.h)#ifndef Unit1H
#define Unit1H
#include
#include
#include
#include
#include
#include // NOTIFYICONDATA, Shell_NotifyIcon
class TForm1 : public TForm {
__published:
TButton *btnTest;
void __fastcall FormCreate(TObject *Sender);
void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
void __fastcall btnTestClick(TObject *Sender);
private:
static UINT WM_TASKBARCREATED; // 工作列重建訊息 ID
static UINT WM_ICONNOTIFY; // 托盤圖示回呼訊息 ID
NOTIFYICONDATA m_nid; // 托盤圖示資料結構
void AddTrayIcon(); // 加入托盤
void RemoveTrayIcon(); // 移除托盤
protected:
virtual void __fastcall WndProc(TMessage &Message); // 重載視窗程序
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;
UINT TForm1::WM_TASKBARCREATED = 0;
UINT TForm1::WM_ICONNOTIFY = 0;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) {}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender) {
// 註冊系統級訊息
WM_TASKBARCREATED = RegisterWindowMessage(L"TaskbarCreated");
WM_ICONNOTIFY = RegisterWindowMessage(L"TrayIconNotify"); // 自訂回呼訊息
AddTrayIcon();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) {
RemoveTrayIcon();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::WndProc(TMessage &Message) {
// 1. 處理托盤圖示回呼 (滑鼠左鍵雙擊、右鍵選單等)
if (Message.Msg == WM_ICONNOTIFY) {
if (Message.LParam == WM_LBUTTONDBLCLK) {
// 雙擊托盤圖示:顯示/隱藏主視窗
Visible ? Hide() : (Show(), BringToFront());
}
else if (Message.LParam == WM_RBUTTONUP) {
// 右鍵:顯示快速選單 (需自行建立 TPopupMenu)
// PopupMenu1->Popup(Mouse->CursorPos.X, Mouse->CursorPos.Y);
}
return;
}
// 2. 關鍵:攔截工作列重建訊息
if (Message.Msg == WM_TASKBARCREATED) {
AddTrayIcon(); // 重新註冊圖示
return;
}
// 3. 其他訊息交給基底類別
TForm::WndProc(Message);
}
//---------------------------------------------------------------------------
void TForm1::AddTrayIcon() {
ZeroMemory(&m_nid, sizeof(m_nid));
m_nid.cbSize = sizeof(NOTIFYICONDATA);
m_nid.hWnd = Handle;
m_nid.uID = 1; // 圖示識別碼 (單一圖示固定為 1)
m_nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE | NIF_SHOWTIP;
m_nid.uCallbackMessage = WM_ICONNOTIFY;
m_nid.hIcon = Application->Icon->Handle; // 使用應用程式圖示
lstrcpyn(m_nid.szTip, Application->Title.c_str(), sizeof(m_nid.szTip)/sizeof(TCHAR));
Shell_NotifyIcon(NIM_ADD, &m_nid);
}
//---------------------------------------------------------------------------
void TForm1::RemoveTrayIcon() {
m_nid.cbSize = sizeof(NOTIFYICONDATA);
m_nid.hWnd = Handle;
m_nid.uID = 1;
Shell_NotifyIcon(NIM_DELETE, &m_nid);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::btnTestClick(TObject *Sender) {
ShowMessage(L"主視窗顯示正常,托盤圖示運作中。");
}
NOTIFYICONDATA_V4_SIZE 或 sizeof(NOTIFYICONDATA) (配合 _WIN32_IE >= 0x0600)。WM_ICONNOTIFY) 用於接收滑鼠事件,lParam 包含 WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_LBUTTONDBLCLK, WM_RBUTTONUP 等。Application->Icon->Handle 取得應用程式主圖示;亦可用 LoadImage 載入獨立 .ico 檔。explorer.exe → 右鍵「結束工作」。uID 區分多個托盤圖示,std::map 管理。LoadImage 配合 LR_DEFAULTSIZE 自動縮放。Shell_NotifyIcon(NIM_SETVERSION, &nid) 設定 nid.uVersion = NOTIFYICON_VERSION_4 以啟用豐富工具提示、氣球通知。CreateMutex 確保僅一實例運行,避免重複註冊托盤圖示。托盤圖示消失是經典的 Explorer 重啟問題,根源在於應用程式未處理 TaskbarCreated 訊息。本文提供的 VCL 方案僅需 3 步驟:註冊訊息 → 重載 WndProc 攔截 → 重建圖示,即可徹底解決。此模式同樣適用於 Delphi、C++ (Win32 SDK)、C# (WndProc 覆寫) 等所有 Windows 桌面開發平台。