菜單是 Windows 應用核心交互介面。MFC 提供資源編輯器設計、命令消息映射、ON_UPDATE_COMMAND_UI 動態狀態更新機制,支持運行時修改菜單文本、勾選狀態、啟用/禁用。本文系統講解菜單資源設計、雙重映射機制、CCmdUI 控制接口、完整實戰代碼模板。
ID_FILE_NEW)& 設置加速鍵,如 &File)「狀態欄說明\n工具欄提示」ID_FILE_NEW: "創建新文檔\n新建"
ID_EDIT_COPY: "複製選中內容到剪貼板\n複製"
ID_VIEW_TOOLBAR: "顯示/隱藏工具欄\n工具欄"
在視圖類 (或文檔/框架/應用類) 中映射:
| 映射宏 | 觸發時機 | 處理函數原型 | 核心作用 |
|---|---|---|---|
ON_COMMAND(id, func) | 用戶選中菜單項 | afx_msg void func() | 執行具體業務邏輯 |
ON_UPDATE_COMMAND_UI(id, func) | 菜單即將顯示 (彈出前) | afx_msg void func(CCmdUI* pCmdUI) | 動態設置啟用/勾選/文本 |
ON_UPDATE_COMMAND_UI 在菜單彈出時高頻調用,邏輯必須輕量ON_COMMAND 映射時自動禁用class CCmdUI {
public:
void Enable(BOOL bOn); // 啟用/禁用 (灰顯)
void SetCheck(int nCheck); // 勾選狀態: 0=無, 1=勾選, 2=單選點
void SetText(LPCTSTR lpszText); // 動態修改顯示文本
void SetRadio(BOOL bOn); // 設置單選狀態 (同 SetCheck)
// 內部狀態
BOOL m_bEnableChanged;
BOOL m_bCheckChanged;
};
class CMyView : public CView {
// ...
afx_msg void OnViewGrid();
afx_msg void OnUpdateViewGrid(CCmdUI* pCmdUI);
afx_msg void OnEditCopy();
afx_msg void OnUpdateEditCopy(CCmdUI* pCmdUI);
BOOL m_bShowGrid; // 狀態變量
// ...
};
BEGIN_MESSAGE_MAP(CMyView, CView)
ON_COMMAND(ID_VIEW_GRID, OnViewGrid)
ON_UPDATE_COMMAND_UI(ID_VIEW_GRID, OnUpdateViewGrid)
ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)
END_MESSAGE_MAP()
// 執行命令:切換網格顯示
void CMyView::OnViewGrid() {
m_bShowGrid = !m_bShowGrid; // 邏輯非
Invalidate(); // 觸發重繪
TRACE(_T("網格顯示: %s\n"), m_bShowGrid ? _T("開") : _T("關"));
}
// UI 更新:動態勾選 + 文本
void CMyView::OnUpdateViewGrid(CCmdUI* pCmdUI) {
pCmdUI->SetCheck(m_bShowGrid ? 1 : 0); // 勾選狀態
pCmdUI->SetText(m_bShowGrid ? _T("隱藏網格") : _T("顯示網格")); // 動態文本
// 無需 Enable,默認啟用
}
// 執行命令:複製
void CMyView::OnEditCopy() {
CopySelectionToClipboard();
}
// UI 更新:根據選中狀態啟用/禁用
void CMyView::OnUpdateEditCopy(CCmdUI* pCmdUI) {
BOOL bHasSelection = HasValidSelection();
pCmdUI->Enable(bHasSelection); // 核心:啟用/禁用
// 可選:動態文本提示
// pCmdUI->SetText(bHasSelection ? _T("複製") : _T("複製 (無選中)"));
}
void CMyApp::OnUpdateRecentFiles(CCmdUI* pCmdUI) {
CMenu* pMenu = pCmdUI->m_pMenu; // 獲取菜單指針
if (!pMenu) return;
// 1. 刪除舊項 (保留分隔符前的固定項)
int count = pMenu->GetMenuItemCount();
for (int i = count - 1; i >= 0; --i) {
UINT id = pMenu->GetMenuItemID(i);
if (id >= ID_FILE_MRU_FILE1 && id <= ID_FILE_MRU_FILE16) {
pMenu->DeleteMenu(i, MF_BYPOSITION);
}
}
// 2. 添加最新文件列表
for (int i = 0; i < m_recentFiles.GetSize(); ++i) {
CString item;
item.Format(_T("&%d %s"), i+1, m_recentFiles[i]);
pMenu->InsertMenu(ID_FILE_MRU_FILE1 + i, MF_BYPOSITION | MF_STRING,
ID_FILE_MRU_FILE1 + i, item);
}
pCmdUI->Enable(m_recentFiles.GetSize() > 0);
}
void CMyView::OnRButtonUp(UINT nFlags, CPoint point) {
ClientToScreen(&point);
CMenu menu;
menu.LoadMenu(IDR_CONTEXT_MENU);
CMenu* pPopup = menu.GetSubMenu(0);
if (pPopup) {
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
point.x, point.y, this);
}
}
void CMyView::AddPluginMenuItems() {
CMenu* pMenu = GetMenu()->GetSubMenu(2); // "工具" 菜單
for (auto& plugin : m_plugins) {
pMenu->AppendMenu(MF_STRING, plugin.cmdId, plugin.name);
// 需在框架/視圖中 ON_COMMAND(plugin.cmdId, OnPluginCmd)
}
}
SetText 文本中 & 會被解析為加速鍵,需雙寫 && 或避免使用SetCheck(1) 勾選,SetCheck(2) 單選點 (圓點),SetCheck(0) 無標記ID_APP_EXIT) 衝突TPM_LEFTALIGN | TPM_RIGHTBUTTON 標準右鍵彈出組合本文整理自 MFC 經典技術文檔,適用於 VC6~VS2022 所有版本。