共用方式為


如果功能表附加至 Visual C++ 中的對話框,就無法從功能表的使用者介面處理程式變更選單項的狀態

本文可協助您解決在 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 處理程式。

解決方法

使用下列步驟來解決此問題:

  1. ON_WM_INITMENUPOPUP將專案新增至訊息對應:

    BEGIN_MESSAGE_MAP(CTestDlg, CDialog)
    // }} AFX_MSG_MAP
    
    ON_WM_INITMENUPOPUP()
    END_MESSAGE_MAP()
    
  2. 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 中重現此行為:

  1. 使用 AppWizard 建立以 MFC 對話框為基礎的應用程式。

  2. 建立新的功能表資源,並將 [檔案] 和 [檔案/結束] 功能表項新增至其中。

  3. 將此功能表設定為對話框中對話框的功能表,屬性視窗。 若要這樣做,請在對話編輯器中開啟對話框資源。 在 [ 屬性] 視窗中,按兩下 [ 選取功能表]。 新功能表資源的識別碼會顯示在 [功能表屬性編輯器] 下拉式清單中。

  4. 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.
    
  5. 建置並執行應用程式。