Professional UI Solutions
Site Map   /  Register
 
 
 

Constructing Menus Dynamically at Run Time

Pop-Up Menus

Prof-UIS menus are not based on HMENU handles. They are based on CExtPopupMenuWnd windows. So you cannot use the WM_INITMENUPOPUP and WM_INITMENU messages here.

There are two registered windows messages in Prof-UIS CExtPopupBaseWnd::g_nMsgPrepareMenu and CExtPopupBaseWnd::g_nMsgPrepareOneMenuLevel (starting from Prof-UIS 2.33). The CExtPopupBaseWnd::g_nMsgPrepareMenu message is sent only once for each menu tree and allows you to analyze the entire menu tree before it appears on the screen. The CExtPopupBaseWnd::g_nMsgPrepareOneMenuLevel message is similar to the previous message but it is sent for each single pop-up menu sublevel before it appears on the screen. This makes it handy to construct menus with many submenus.

Let's assume you want to insert some set of new menu items at a certain position in a pop-up menu. Open the menu in the resource editor and insert a new menu item, for example, specified with ID_MY_MENU_MARKER (see Figure 1 and Figure 2). The text and other properties of this menu item are not important and can be arbitrary.

Menu marker in the resource editor

Figure 1. Menu marker in the resource editor

Menu marker properties

Figure 2. Menu marker properties

The CExtPopupBaseWnd::g_nMsgPrepareOneMenuLevel registered windows message allows you to replace this helper menu item with a set of dynamic menu items on-the-fly immediately before the menu appears on the screen. So what you need to do is to find ID_MY_MENU_MARKER and replace it with new items. Here is the method declaration, message map's entry and implementation.

// Method declaration
afx_msg LRESULT OnExtMenuPrepareOneLevel(WPARAM wParam, LPARAM lParam);


// Message map entry
ON_REGISTERED_MESSAGE(
    CExtPopupBaseWnd::g_nMsgPrepareOneMenuLevel,
    OnExtMenuPrepareOneLevel
    )

    
// Method implementation
LRESULT CMainFrame::OnExtMenuPrepareOneLevel( WPARAM wParam, LPARAM lParam )
{
    lParam; // unused parameter
    CExtPopupMenuWnd::MsgPrepareMenuData_t * pData =
        reinterpret_cast < CExtPopupMenuWnd::MsgPrepareMenuData_t * > ( wParam );
    ASSERT( pData != NULL );
    CExtPopupMenuWnd * pPopup = pData->m_pPopup;
    ASSERT( pPopup != NULL );
    INT nReplacePos =
        pPopup->ItemFindPosForCmdID( ID_MY_MENU_MARKER );
    if( nReplacePos < 0 )
        return 0;
    VERIFY( pPopup->ItemRemove(nReplacePos) );
    // insert one command
    pPopup->ItemInsertCommand(
        ID_APP_ABOUT,
        nReplacePos,
        "About (1)"
        );
    // insert one more command
    pPopup->ItemInsertCommand(
        ID_APP_ABOUT,
        nReplacePos + 1,
        "About (2)"
        );
    // and insert one more command
    pPopup->ItemInsertCommand(
        ID_APP_ABOUT,
        nReplacePos + 2,
        "About (3)"
        );
    return 1L;
}

As you can see, you can use the CExtPopupMenuWnd::ItemFindPosForCmdID() method to find a menu item by the command identifier. If the item is not found, the method returns -1.

If the pop-up menu has been modified, the pData->m_bMenuChanged property should be set to true. To cancel tracking the pop-up menu, set pData->m_bMenuCanceled to true. Figure 3 shows the result of replacing ID_MY_MENU_MARKER with three new menu items.

The helper menu item is replaced with three new items

Figure 3. The helper menu item is replaced with three new items

Menu Bar

You can also add/remove any top level item to/from the menu bar (CExtMenuControlBar), which is based on the toolbar (CExtToolControlBar). The CExtMenuControlBar::UpdateMenuBar() method invokes an algorithm that rebuilds menu bar's buttons. You can implement your own algorithm of filling the menu bar with buttons (CExtBarButton). Create your own CExtMenuControlBar-derived class and override the CExtMenuControlBar::_UpdateMenuBar() internal virtual method. Your method can be either based on the original method (you only embed buttons you want) or completely rebuild the array of buttons in your own manner. All the commands inside the menus attached to the menu bar buttons should be registered in the command manager. The top level buttons in the menu bar also have their unique command identifiers and should be allocated exactly as it is done in the CExtMenuControlBar::_UpdateMenuBar() method.

Let's assume you need to create a copy of the View menu. Open the resource editor and insert a new menu item, for example, specified with ID_MY_MENU_MARKER (see Figure 4 and Figure 5). The text and other properties of this menu item are not important and can be arbitrary.

Menu marker in the menu line open in the resource editor

Figure 4. Menu marker in the menu line open in the resource editor

Menu marker properties

Figure 5. Menu marker properties

Here is the code of a CExtMenuControlBar-derived class that creates a copy of the View menu (see Figure 6):

//Class declaration:
class CYourMenuControlBar : public CExtMenuControlBar
{
protected:
 virtual BOOL _UpdateMenuBar(
  BOOL bDoRecalcLayout = TRUE
  );
};


//Class implementation:
BOOL CYourMenuControlBar::_UpdateMenuBar( BOOL bDoRecalcLayout )
{
    SetButtons(); // remove all buttons
 
    VERIFY(
        g_CmdManager->CmdRemoveByMask(
            g_CmdManager->ProfileNameFromWnd( GetSafeHwnd() ),
            (DWORD)CExtCmdItem::STATE_MENUBAR_TMP
            )
        );
 
#if (!defined __EXT_MFC_NO_CUSTOMIZE)
    MenuInfoUpdate();
    CExtCustomizeSite::CCmdMenuInfo * pMenuInfo = MenuInfoGet();
    if( pMenuInfo != NULL )
    {
        ASSERT_VALID( pMenuInfo->GetNode() );
        SetButtons( pMenuInfo->GetNode() );
    }
    else
#endif // (!defined __EXT_MFC_NO_CUSTOMIZE)
    {
        CMenu * pMenu = GetMenu();
        if( pMenu != NULL && pMenu->GetSafeHmenu() != NULL )
        {
            ASSERT( ::IsMenu(pMenu->GetSafeHmenu()) );
            bool bRevertRTL = OnQueryRevertRTL();
            UINT nMenuItemCount = pMenu->GetMenuItemCount();
            for( UINT nMenuItemIndex = 0; nMenuItemIndex < nMenuItemCount; nMenuItemIndex++ )
            {
                UINT nInsertButtonLocation = 
                    bRevertRTL ? 0 : nMenuItemIndex;
                MENUITEMINFO mii;
                ::memset( &mii, 0, sizeof(MENUITEMINFO) );
                mii.cbSize = sizeof(MENUITEMINFO);
                mii.fMask =
                    MIIM_CHECKMARKS
                    |MIIM_DATA
                    |MIIM_ID
                    |MIIM_STATE
                    |MIIM_SUBMENU
                    |MIIM_TYPE;
                mii.cch = __MAX_UI_ITEM_TEXT;
                CExtSafeString sText;
                mii.dwTypeData = sText.GetBuffer( __MAX_UI_ITEM_TEXT );
                ASSERT( mii.dwTypeData != NULL );
                if( mii.dwTypeData == NULL )
                {
                    ASSERT( FALSE );
                    return FALSE;
                }
                if( ! pMenu->GetMenuItemInfo( nMenuItemIndex, &mii, TRUE ) )
                {
                    sText.ReleaseBuffer();
                    ASSERT( FALSE );
                    return false;
                }
                sText.ReleaseBuffer();
 
                BOOL bAppendMdiWindowsMenu = FALSE;
                UINT nCmdID = 0;
                CExtCmdItem * pCmdItem = NULL;
                if( mii.hSubMenu == NULL )
                {
                    nCmdID = mii.wID;
                    if( nCmdID == ID_SEPARATOR )
                    {
                        if( ! InsertButton( nInsertButtonLocation, nCmdID, FALSE ) )
                        {
                            ASSERT( FALSE );
                            return FALSE;
                        }
                        continue;
                    }
                    ASSERT( CExtCmdManager::IsCommand(nCmdID) );
                    pCmdItem =
                        g_CmdManager->CmdGetPtr(
                            g_CmdManager->ProfileNameFromWnd( GetSafeHwnd() ),
                            nCmdID
                            );
                    ASSERT( pCmdItem != NULL );
                }
                else
                {
                    pCmdItem =
                        g_CmdManager->CmdAllocPtr( 
                            g_CmdManager->ProfileNameFromWnd( GetSafeHwnd() )
                            );
                    if( pCmdItem == NULL )
                    {
                        ASSERT( FALSE );
                        return FALSE;
                    }
                    nCmdID = pCmdItem->m_nCmdID;
                    ASSERT( CExtCmdManager::IsCommand(nCmdID) );
                    pCmdItem->StateSetMenubarTemp();
                    pCmdItem->StateSetBasic();
 
                    if( _IsMdiApp() && (! m_sMdiWindowPopupName.IsEmpty() ) )
                    {
                        CExtSafeString _sText(sText);
                        _sText.TrimLeft();
                        _sText.TrimRight();
                        while( _sText.Replace(_T("&"),_T("")) > 0 )
                        {
                            _sText.TrimLeft();
                            _sText.TrimRight();
                        }
                        if( _sText == m_sMdiWindowPopupName )
                            bAppendMdiWindowsMenu = TRUE;
                    }
                }
                ASSERT( pCmdItem != NULL );
                if( pCmdItem->m_sToolbarText.IsEmpty() )
                    pCmdItem->m_sToolbarText = sText;
                if( pCmdItem->m_sMenuText.IsEmpty() )
                    pCmdItem->m_sMenuText = sText;
 

                ///////////////////// BEGIN ///////////////////////////
 
                if( ! InsertButton( nInsertButtonLocation, nCmdID, FALSE ) )
                {
                    ASSERT( FALSE );
                    return FALSE;
                }
 
                if( nCmdID == ID_MY_MENU_MARKER )
                {
                    CMenu * pMenu = GetMenu();
                    if( pMenu != NULL && pMenu->GetSafeHmenu() != NULL )
                    {
                        CMenu * pSubMenu = pMenu->GetSubMenu(2);
                        if( pSubMenu != NULL && pSubMenu->GetSafeHmenu() != NULL )
                            SetButtonMenu(
                                nInsertButtonLocation,
                                pSubMenu->GetSafeHmenu(),
                                FALSE,
                                FALSE,
                                FALSE
                                );
                    }
                }
 
                ///////////////////// END ///////////////////////////
                if( mii.hSubMenu != NULL )
                {
                    ASSERT( ::IsMenu(mii.hSubMenu) );
                    SetButtonMenu(
                        nInsertButtonLocation,
                        mii.hSubMenu,
                        FALSE,
                        FALSE,
                        FALSE
                        );
                }
                if( bAppendMdiWindowsMenu )
                {
                    VERIFY( MarkButtonAsMdiWindowsMenu( nInsertButtonLocation, TRUE ) );
                }
            }
 
            ASSERT( m_pRightBtn == NULL );
            m_pRightBtn = OnCreateBarRightBtn();
            if( m_pRightBtn != NULL )
            {
                ASSERT_VALID( m_pRightBtn );
                ASSERT_KINDOF( CExtBarContentExpandButton, m_pRightBtn );
                m_buttons.Add( m_pRightBtn );
            }
        }
    }
    if( _IsMdiApp() )
    {
        if( !IsOleIpObjActive() )
            if( _InstallMdiDocButtons( FALSE ) )
                bDoRecalcLayout = TRUE;
        VERIFY( _SyncActiveMdiChild() );
    }
    if( bDoRecalcLayout )
    {
        Invalidate();
        _RecalcLayoutImpl();
        UpdateWindow();
        if( m_pDockSite != NULL )
        {
            CFrameWnd * pFrame = GetParentFrame();
            ASSERT_VALID( pFrame );
            if( pFrame->IsKindOf(RUNTIME_CLASS(CExtMiniDockFrameWnd)) )
                pFrame->SetWindowPos(
                    NULL, 0, 0, 0, 0,
                    SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOSIZE
                    |SWP_NOZORDER|SWP_NOOWNERZORDER
                    |SWP_FRAMECHANGED
                    );
        }
    }
    return TRUE;
}

Do not forget to include the ExtMiniDockFrameWnd.h header in your source file:

 
#ifndef __EXTMINIDOCKFRAMEWND_H
 #include <../Src/ExtMiniDockFrameWnd.h>
#endif

The copy of the View menu

Figure 6. The copy of the View menu

In the same way you can hide a top menu item at the specified position by using the following code (use this code instead of the code marked with BEGIN and END):

///////////////////// BEGIN ///////////////////////////
 
if( ! InsertButton( nInsertButtonLocation, nCmdID, FALSE ) )
{
    ASSERT( FALSE );
    return FALSE;
}
 
if( nInsertButtonLocation == 2 ) // some position
{
    CExtBarButton * pTBB =
        _GetButtonPtr( nInsertButtonLocation );
    ASSERT( pTBB != NULL );
    ASSERT_VALID( pTBB );
 
    pTBB->SetStyle( 
        pTBB->GetStyle() | TBBS_HIDDEN
        );
}
 
///////////////////// END ///////////////////////////

Built-In Menus

You can modify or completely suppress any built-in pop-up menu (e.g., the menu filled with a list of toolbars and control bars). This is described in this FAQ.

Back To Top Other Articles...