// TrayApp.h
class CTrayApp : public CWinApp {
CMainFrame* m_pFrame = nullptr;
NOTIFYICONDATA m_nid = { sizeof(NOTIFYICONDATA) };
UINT m_uTaskbarRestart = 0;
static const UINT WM_TRAYNOTIFY = WM_USER + 1;
public:
virtual BOOL InitInstance() override;
void SetupTrayIcon();
void RemoveTrayIcon();
afx_msg LRESULT OnTaskbarRestart(WPARAM, LPARAM);
DECLARE_MESSAGE_MAP()
};
// TrayApp.cpp
BEGIN_MESSAGE_MAP(CTrayApp, CWinApp)
ON_REGISTERED_MESSAGE(WM_TRAYNOTIFY, OnTrayNotify) // 若用 ON_MESSAGE 需靜態
ON_REGISTERED_MESSAGE(RegisterWindowMessage(_T("TaskbarCreated")), OnTaskbarRestart)
END_MESSAGE_MAP()
BOOL CTrayApp::InitInstance() {
m_pFrame = new CMainFrame;
if (!m_pFrame->LoadFrame(IDR_MAINFRAME)) return FALSE;
m_pFrame->ShowWindow(SW_HIDE); // 關鍵:隱藏主視窗
m_pFrame->UpdateWindow();
m_uTaskbarRestart = RegisterWindowMessage(_T("TaskbarCreated"));
SetupTrayIcon();
return TRUE;
}
void CTrayApp::SetupTrayIcon() {
m_nid.hWnd = m_pFrame->m_hWnd;
m_nid.uID = IDR_MAINFRAME;
m_nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
m_nid.uCallbackMessage = WM_TRAYNOTIFY;
m_nid.hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
_tcscpy_s(m_nid.szTip, _T("My Tray Application"));
Shell_NotifyIcon(NIM_ADD, &m_nid);
}
void CTrayApp::RemoveTrayIcon() {
Shell_NotifyIcon(NIM_DELETE, &m_nid);
}
LRESULT CTrayApp::OnTaskbarRestart(WPARAM, LPARAM) {
SetupTrayIcon(); // 任務列重建後重新加入
return 0;
}
// MainFrm.cpp 訊息處理
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_MESSAGE(WM_TRAYNOTIFY, OnTrayNotify)
END_MESSAGE_MAP()
LRESULT CMainFrame::OnTrayNotify(WPARAM wParam, LPARAM lParam) {
if (wParam != IDR_MAINFRAME) return 0;
switch (lParam) {
case WM_LBUTTONDBLCLK:
ShowWindow(SW_SHOW); // 雙擊顯示主視窗
SetForegroundWindow();
break;
case WM_RBUTTONUP: {
CMenu menu; menu.LoadMenu(IDR_TRAY_MENU);
CMenu* pPopup = menu.GetSubMenu(0);
CPoint pt; GetCursorPos(&pt);
SetForegroundWindow(); // 關鍵:否則選單不會在點擊外消失
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, this);
break;
}
case WM_MOUSEMOVE:
// 可動態更新提示文字
_tcscpy_s(m_nid.szTip, _T("Mouse over tray icon"));
Shell_NotifyIcon(NIM_MODIFY, &m_nid);
break;
}
return 0;
}
| 問題 | 原因 | 解法 |
|---|---|---|
| 右鍵選單彈出後不隨點擊外部消失 | 未呼叫 `SetForegroundWindow()` | `TrackPopupMenu` 前呼叫 `SetForegroundWindow()` |
| 雙擊顯示視窗後,單擊任務列圖示無法再次隱藏 | 視窗狀態邏輯混亂 | 維護 `bool m_bVisible` 狀態,切換時同步 `ShowWindow` 與選單項目勾選狀態 |
| 任務列重啟 (Explorer.exe 重啟) 後圖示消失 | 未處理 `TaskbarCreated` | `ON_REGISTERED_MESSAGE(RegisterWindowMessage(_T("TaskbarCreated")), OnTaskbarRestart)` 重 `NIM_ADD` |
| 圖示模糊/尺寸錯誤 | 單一解析度圖示 / 未宣告 DPI 感知 | 多解析度 `.ico` + Manifest `dpiAware` |
| 記憶體洩漏:退出時未 `NIM_DELETE` | 未在 `ExitInstance`/`OnDestroy` 清理 | 覆寫 `ExitInstance` 呼叫 `RemoveTrayIcon()` |
系統匣盤程式看似簡單,**訊息迴路、圖示生命週期、高 DPI、任務列重建、選單焦點** 細節決定體驗品質。掌握 MFC 經典實作後,**建議新專案直接採用現代原生/跨平台框架**,避免維護傳統 MFC 技術債。