本文可協助您解決在 Visual C++中將功能表附加至對話框時所發生的問題。
原始產品版本: Visual C++、.NET 2002
原始 KB 編號: 242577
徵兆
注意
Microsoft Visual C++ .NET 2002 和 Visual C++ .NET 2003 都支援 .NET Framework 和 Unmanaged 原生 Windows 程式代碼模型所提供的 Managed 程式代碼模型。 本文中的資訊僅適用於 Unmanaged Visual C++ 程式代碼。 Visual C++ 2005 同時支援 .NET Framework 和 Unmanaged 原生 Windows 程式代碼模型所提供的 Managed 程式代碼模型。
如果選單附加至對話框,從其命令使用者介面 (UI) 處理程式變更選單項的狀態(啟用/停用、核取/取消核取、變更文字)將無法正常運作:
void CTestDlg::OnUpdateFileExit(CCmdUI* pCmdUI)
{
pCmdUI->Enable(FALSE); //Not calling the command handler, but does not show as disabled.
pCmdUI->SetCheck(TRUE); // Does not show check mark before the text.
pCmdUI->SetRadio(TRUE); // Does not show dot before the text.
pCmdUI->SetText("Close"); //Does not change the text.
}
原因
顯示下拉功能表時,會在 WM_INITMENUPOPUP
顯示功能表項之前傳送訊息。 MFC CFrameWnd::OnInitMenuPopup
函式會逐一查看功能表項,並呼叫專案的更新命令 UI 處理程式,如果有的話。 每個功能表項的外觀都會更新,以反映其狀態(已啟用/停用、已核取/未核取)。
更新UI機制不適用於對話框型應用程式,因為 CDialog
沒有 OnInitMenuPopup
處理程式,而且它使用 CWnd 的預設處理程式,它不會呼叫功能表項的更新命令 UI 處理程式。
解決方法
使用下列步驟來解決此問題:
ON_WM_INITMENUPOPUP
將專案新增至訊息對應:BEGIN_MESSAGE_MAP(CTestDlg, CDialog) // }} AFX_MSG_MAP ON_WM_INITMENUPOPUP() END_MESSAGE_MAP()
OnInitMenuPopup
將成員函式新增至您的對話框類別,並複製下列程式代碼:注意
此程序代碼主要取自 CFrameWnd::OnInitMenuPopup in WinFrm.cpp)
void CTestDlg::OnInitMenuPopup(CMenu *pPopupMenu, UINT nIndex,BOOL bSysMenu)
{
// Make sure this is actually a menu. When clicking the program icon
// in the window title bar this function will trigger and pPopupMenu
// will NOT be a menu.
if (!IsMenu(pPopupMenu->m_hMenu))
return;
ASSERT(pPopupMenu != NULL);
// Check the enabled state of various menu items.
CCmdUI state;
state.m_pMenu = pPopupMenu;
ASSERT(state.m_pOther == NULL);
ASSERT(state.m_pParentMenu == NULL);
// Determine if menu is popup in top-level menu and set m_pOther to
// it if so (m_pParentMenu == NULL indicates that it is secondary popup).
HMENU hParentMenu;
if (AfxGetThreadState()->m_hTrackingMenu == pPopupMenu->m_hMenu)
state.m_pParentMenu = pPopupMenu; // Parent == child for tracking popup.
else if ((hParentMenu = ::GetMenu(m_hWnd))!= NULL)
{
CWnd* pParent = this;
// Child windows don't have menus--need to go to the top!
if (pParent != NULL &&
(hParentMenu = ::GetMenu(pParent->m_hWnd))!= NULL)
{
int nIndexMax = ::GetMenuItemCount(hParentMenu);
for (int nIndex = 0; nIndex < nIndexMax; nIndex++)
{
if (::GetSubMenu(hParentMenu, nIndex) == pPopupMenu->m_hMenu)
{
// When popup is found, m_pParentMenu is containing menu.
state.m_pParentMenu = CMenu::FromHandle(hParentMenu);
break;
}
}
}
}
state.m_nIndexMax = pPopupMenu->GetMenuItemCount();
for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax;
state.m_nIndex++)
{
state.m_nID = pPopupMenu->GetMenuItemID(state.m_nIndex);
if (state.m_nID == 0)
continue; // Menu separator or invalid cmd - ignore it.
ASSERT(state.m_pOther == NULL);
ASSERT(state.m_pMenu != NULL);
if (state.m_nID == (UINT)-1)
{
// Possibly a popup menu, route to first item of that popup.
state.m_pSubMenu = pPopupMenu->GetSubMenu(state.m_nIndex);
if (state.m_pSubMenu == NULL ||
(state.m_nID = state.m_pSubMenu->GetMenuItemID(0)) == 0 ||
state.m_nID == (UINT)-1)
{
continue; // First item of popup can't be routed to.
}
state.DoUpdate(this, TRUE); // Popups are never auto disabled.
}
else
{
// Normal menu item.
// Auto enable/disable if frame window has m_bAutoMenuEnable
// set and command is _not_ a system command.
state.m_pSubMenu = NULL;
state.DoUpdate(this, FALSE);
}
// Adjust for menu deletions and additions.
UINT nCount = pPopupMenu->GetMenuItemCount();
if (nCount < state.m_nIndexMax)
{
state.m_nIndex -= (state.m_nIndexMax - nCount);
while (state.m_nIndex < nCount &&
pPopupMenu->GetMenuItemID(state.m_nIndex) == state.m_nID)
{
state.m_nIndex++;
}
}
state.m_nIndexMax = nCount;
}
}
狀態
這是依照設計的行為。
其他相關資訊
更新命令 UI 處理程式也會從 CWnd::OnCommand
呼叫,以確保在路由之前尚未停用命令。 這就是為什麼命令處理程式不會針對停用的功能表項呼叫,即使它不是灰色(無法使用)。 功能表項不會繪製,以反映其在此案例中的狀態。 這是來自Wincore.cpp檔案的相關程序代碼:
// Make sure command has not become disabled before routing.
CTestCmdUI state;
state.m_nID = nID;
OnCmdMsg(nID, CN_UPDATE_COMMAND_UI, &state, NULL);
if (!state.m_bEnabled)
{
TRACE1("Warning: not executing disabled command %d\n", nID);
return TRUE;
}
重現行為的步驟
請遵循下列步驟,在 Visual C++ .NET 中重現此行為:
使用 AppWizard 建立以 MFC 對話框為基礎的應用程式。
建立新的功能表資源,並將 [檔案] 和 [檔案/結束] 功能表項新增至其中。
將此功能表設定為對話框中對話框的功能表,屬性視窗。 若要這樣做,請在對話編輯器中開啟對話框資源。 在 [ 屬性] 視窗中,按兩下 [ 選取功能表]。 新功能表資源的識別碼會顯示在 [功能表屬性編輯器] 下拉式清單中。
UPDATE_COMMAND_UI
新增 [檔案/結束] 功能表項的處理程式。 若要這樣做,請在功能表編輯器中以滑鼠右鍵按兩下 [檔案/結束 ],然後按兩下 [ 新增事件處理程式]。 在 [事件處理程序精靈] 中UPDATE_COMMAND_UI
,將處理程式新增至專案CDialog
衍生類別。 單擊 [新增 ] 和 [編輯 ] 以建立處理程式,然後將其中一個語句新增至產生的處理程式方法:pCmdUI->Enable(FALSE); //Not calling the handler, but does not show as disabled pCmdUI->SetCheck(TRUE); // Does not show check mark before the text. pCmdUI->SetRadio(TRUE); // Does not show dot before the text. pCmdUI->SetText("Close"); //Does not change the text.
建置並執行應用程式。