讓對話框程式最小化時隱藏主視窗、在任務列通知區域 (系統托盤) 顯示圖標,右鍵彈出選單可「還原」或「退出」,是背景服務類應用 (如 IM、下載器、監控) 的標準交互。本文提供基於 NOTIFYICONDATA 與 Shell_NotifyIcon 的完整實現代碼。
typedef struct _NOTIFYICONDATA {
DWORD cbSize; // sizeof(NOTIFYICONDATA)
HWND hWnd; // 接收通知訊息的視窗
UINT uID; // 圖標識別碼 (自訂)
UINT uFlags; // NIF_MESSAGE | NIF_ICON | NIF_TIP
UINT uCallbackMessage; // 自訂訊息 (如 WM_MY_TRAY = WM_USER+1)
HICON hIcon; // 圖標句柄
TCHAR szTip[128]; // 滑鼠懸停提示文字
} NOTIFYICONDATA;
BOOL Shell_NotifyIcon(DWORD dwMessage, NOTIFYICONDATA* lpData);
// dwMessage: NIM_ADD (添加), NIM_MODIFY (修改), NIM_DELETE (刪除)
// .h
#define WM_MY_TRAY_MSG (WM_USER + 1)
afx_msg LRESULT OnTrayNotify(WPARAM wParam, LPARAM lParam);
// .cpp 訊息映射
ON_MESSAGE(WM_MY_TRAY_MSG, OnTrayNotify)
BOOL CMyDlg::OnInitDialog() {
CDialog::OnInitDialog();
NOTIFYICONDATA nid = {0};
nid.cbSize = sizeof(nid);
nid.hWnd = m_hWnd;
nid.uID = IDR_MAINFRAME; // 使用應用程式圖標 ID
nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
nid.uCallbackMessage = WM_MY_TRAY_MSG;
nid.hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
_tcscpy_s(nid.szTip, _T("我的程式 - 雙擊還原"));
Shell_NotifyIcon(NIM_ADD, &nid);
return TRUE;
}
LRESULT CMyDlg::OnTrayNotify(WPARAM wParam, LPARAM lParam) {
if (wParam != IDR_MAINFRAME) return 0;
switch (lParam) {
case WM_LBUTTONDBLCLK: // 雙擊左鍵還原
ShowWindow(SW_RESTORE);
SetForegroundWindow(); // 置頂
break;
case WM_RBUTTONUP: { // 右鍵彈出選單
CMenu menu;
menu.LoadMenu(IDR_TRAY_MENU); // 資源建立選單 IDR_TRAY_MENU
CMenu* pPopup = menu.GetSubMenu(0);
if (pPopup) {
CPoint pt; GetCursorPos(&pt);
// TPM_LEFTALIGN | TPM_RIGHTBUTTON 讓右鍵也能選擇
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, this);
}
break;
}
}
return 0;
}
void CMyDlg::OnTrayRestore() { ShowWindow(SW_RESTORE); SetForegroundWindow(); }
void CMyDlg::OnTrayExit() {
// 退出前必須移除托盤圖標,否則殘留「幽靈圖標」
NOTIFYICONDATA nid = {0};
nid.cbSize = sizeof(nid);
nid.hWnd = m_hWnd;
nid.uID = IDR_MAINFRAME;
Shell_NotifyIcon(NIM_DELETE, &nid);
EndDialog(IDOK); // 或 PostMessage(WM_CLOSE)
}
void CMyDlg::OnSysCommand(UINT nID, LPARAM lParam) {
if ((nID & 0xFFF0) == SC_MINIMIZE) {
ShowWindow(SW_HIDE); // 隱藏而非最小化到任務列
return;
}
if ((nID & 0xFFF0) == SC_CLOSE) {
// 視需求決定:真正退出 或 也隱藏到托盤
// 這裡示範真正退出 (調用 OnTrayExit 邏輯)
OnTrayExit();
return;
}
CDialog::OnSysCommand(nID, lParam);
}
void CMyDlg::OnDestroy() {
NOTIFYICONDATA nid = {0};
nid.cbSize = sizeof(nid);
nid.hWnd = m_hWnd;
nid.uID = IDR_MAINFRAME;
Shell_NotifyIcon(NIM_DELETE, &nid);
CDialog::OnDestroy();
}
IDR_MAINFRAME (16x16, 32x32 皆含)。IDR_TRAY_MENU,含「還原」(ID_TRAY_RESTORE)、分隔線、「退出」(ID_TRAY_EXIT),並用 ClassWizard 映射命令處理函數。_tcscpy_s / StringCchCopy,避免 strcpy 緩衝區溢出。NIM_DELETE 會留下圖標,滑鼠劃過才消失。OnDestroy 刪除是最後防線。LoadImage 載入 PNG 圖標 (IMAGE_ICON, LR_DEFAULTSIZE)。uID 需區分 (如 IDR_MAINFRAME + GetCurrentProcessId()),否則第二個實例 NIM_ADD 會失敗。此模式已廣泛應用於即時通訊、備份同步、系統監控等長期駐留型應用。