彈出式選單 (Popup Menu/Context Menu) 是無標題列、浮動於游標位置的選單,通常由滑鼠右鍵觸發 (WM_CONTEXTMENU),也可程式控制在任意位置顯示 (WM_LBUTTONDOWN、熱鍵、長按等)。MFC 透過 CMenu 類別封裝 Win32 選單 API。
IDR_POPUP_MENU (或 IDC_POPMENU)。對每個子選單項 (如 ID_POPUP_COPY、ID_POPUP_DELETE) 加入 COMMAND 訊息處理函式,邏輯與主選單項相同。
// View 或 Dialog 類別
void CMyView::OnContextMenu(CWnd* pWnd, CPoint point)
{
// point 為螢幕座標
CMenu menu;
if (!menu.LoadMenu(IDR_POPUP_MENU)) return;
// 取得第一層子選單 (索引 0)
CMenu* pPopup = menu.GetSubMenu(0);
if (!pPopup) return;
// 彈出選單:左對齊、游標位置、擁有者視窗
pPopup->TrackPopupMenu(
TPM_LEFTALIGN | TPM_RIGHTBUTTON, // 右鍵選單標準旗標
point.x, point.y,
this, // 擁有者視窗,接收 COMMAND 訊息
NULL // 不限制選單區域
);
// 選單銷毀時自動釋放 CMenu 物件 (附屬於 menu 區域變數)
}
| 旗標 | 用途 |
|---|---|
TPM_LEFTALIGN | 選單左緣對齊 x 座標 (標準) |
TPM_CENTERALIGN | 選單水平置中於 x 座標 |
TPM_RIGHTALIGN | 選單右緣對齊 x 座標 |
TPM_RIGHTBUTTON | 允許右鍵選取項目 (配合 WM_CONTEXTMENU 必加) |
TPM_LEFTBUTTON | 僅左鍵選取 (預設) |
TPM_NONOTIFY | 不發送 WM_COMMAND,改由回傳值取得選中項目 ID |
TPM_RETURNCMD | 配合 TPM_NONOTIFY,直接回傳選中命令 ID |
void CMyView::OnLButtonDown(UINT nFlags, CPoint point)
{
// point 為用戶區座標,必須轉為螢幕座標
ClientToScreen(&point);
CMenu menu;
menu.LoadMenu(IDR_POPUP_MENU);
CMenu* pPopup = menu.GetSubMenu(0);
if (pPopup)
{
pPopup->TrackPopupMenu(
TPM_LEFTALIGN, // 左鍵觸發不需 TPM_RIGHTBUTTON
point.x, point.y,
this, NULL);
}
// 還原 point 為用戶區座標 (若後續基類處理需要)
ScreenToClient(&point);
CView::OnLButtonDown(nFlags, point);
}
在彈出前即時調整:
void CMyView::OnContextMenu(CWnd* pWnd, CPoint point)
{
CMenu menu;
menu.LoadMenu(IDR_POPUP_MENU);
CMenu* pPopup = menu.GetSubMenu(0);
if (!pPopup) return;
// 根據應用狀態動態更新
pPopup->EnableMenuItem(ID_POPUP_PASTE,
m_bClipboardHasData ? MF_ENABLED : MF_GRAYED); // 由狀態決定啟用/灰顯
pPopup->CheckMenuItem(ID_POPUP_VIEW_GRID,
m_bShowGrid ? MF_CHECKED : MF_UNCHECKED); // 打勾狀態
pPopup->ModifyMenu(ID_POPUP_RENAME, MF_BYCOMMAND, ID_POPUP_RENAME,
m_strSelectedFile.IsEmpty() ? _T("重新命名") : _T("重新命名: ") + m_strSelectedFile);
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this, NULL);
}
& 字元指定快速鍵 (如 &Copy\tCtrl+C),\t 後為顯示快捷鍵提示。this) 具有焦點或呼叫 SetForegroundWindow(),否則鍵盤導航可能失效。| 現象 | 原因 | 解決 |
|---|---|---|
| 選單不顯示 | LoadMenu 失敗、GetSubMenu 索引錯誤、point 座標錯誤 | 檢查資源 ID、確認子選單索引 (通常為 0)、ClientToScreen 轉換 |
| 選單閃爍即消失 | 未設 TPM_RIGHTBUTTON (右鍵觸發時)、擁有者視窗非前景 | 加上 TPM_RIGHTBUTTON、TrackPopupMenu 前呼叫 SetForegroundWindow() |
| 命令不觸發 | 訊息映射缺失、命令 ID 錯誤、擁有者視窗不接收訊息 | ClassWizard 加入 ON_COMMAND、確認 ID 一致、擁有者為 CWnd 派生類 |
| 圖示不顯示 | MFC 預設不支援選單項圖示 | 使用 Owner-draw (OD) 選單或第三方庫 (BCGControlBar, Ultimate Toolbox) |
支援 Office 風格、圖示、分隔線、動畫、鍵盤搜尋、觸控友善:
#include
void CMyView::OnContextMenu(CWnd* pWnd, CPoint point)
{
CMFCPopupMenu* pMenu = new CMFCPopupMenu;
pMenu->Create(this, point.x, point.y, IDR_POPUP_MENU); // 直接用資源 ID
// 自動管理生命週期,點擊外部自動銷毀
}
彈出式選單是桌面應用交互的核心元件。掌握 CMenu::LoadMenu、GetSubMenu、TrackPopupMenu 三步驟核心流程,配合座標轉換 (ClientToScreen) 與動態項目狀態更新,即可實現專業級右鍵選單。現代專案建議直接使用 CMFCPopupMenu 獲得更豐富視覺與交互體驗。