Professional User Interface Suite, Copyright FOSS Software Inc. Help Published with Permission.

The CExtPopupMenuWnd class implements a pop-up menu. To use it is no more difficult than the standard CMenu class.

1. Create a CExtPopupMenuWnd object dynamically:

CExtPopupMenuWnd * pPopup =
new CExtPopupMenuWnd;

2. Initialize menu items by loading the desired menu resource:

VERIFY(
pPopup->LoadMenu(
hWndCommandReceiver,
IDR_TEST_POPUP_MENU
true, // bPopupMenu
false // bNoRefToCmdMngr
)
);

or by making a number of calls to the CExtPopupMenuWnd::ItemInsert() method.

Alternatively you can construct the pop-up menu by using an existing CMenu pointer:

CMenu * pBuildMenu = . . . 
VERIFY(
pPopup->UpdateFromMenu(
hWndCommandReceiver,
pBuildMenu
true, // bPopupMenu
true, // bTopLevel
false // bNoRefToCmdMngr
)
);

In this case the bTopLevel argument should be set to true if the constructed pop-up menu is of top level. The false value means that the menu is used as a submenu of a pop-up menu. In both cases the bPopupMenu argument should be set to true if the first pop-up item of the specified menu object or resource is used for constructing the content of the Prof-UIS pop-up menu. This argument should be false if the content of the specified menu object or resource should be used as is.

The created pop-up menu tree like structure contains command items based on the command descriptions in the command manager. In the some cases (especially in cases of dynamically constructed menus), it is handy to avoid any reference to the command manager and generate absolutely independent command identifiers. This can be done by setting the bNoRefToCmdMngr argument to true or by making a set of calls to the CExtPopupMenuWnd::ItemInsertCommand() method.

3. Let us now take a look at how a new pop-up submenu can be inserted. First, create and initialize it by going through Step 1 and Step 2. Then, call ItemInsertSpecPopup() for inserting a new pop-up at the specified position in the existing CExtPopupMenuWnd object. The code below inserts a color picker menu into an existing menu:

pPopup->ItemInsertSpecPopup(
new CExtPopupColorMenuWnd,
-1,
_T("test color menu")
);

4. Track the created and initialized pop-up menu:

VERIFY(
pPopup->TrackPopupMenu(
0, // dwTrackFlags
x,
y
)
);

NOTE: a) The pop-up menu's tracking position is set in screen coordinates. b) The CExtPopupMenuWnd objects are auto-destroyed, so do not call the delete operator for these objects.

It is handy in some cases to make the CExtPopupMenuWnd::TrackPopupMenu() method keep the code execution control until the menu window is visible on the screen. This can be done by specifying the TPMX_DO_MESSAGE_LOOP flag in the dwTrackFlags parameter. You can specify the TPMX_SELECT_ANY flag if the pop-up menu should appear with the automatically selected first enabled menu item at the top. It is possible to display menu fully expanded by specifying the TPMX_NO_HIDE_RARELY flag. The TPMX_NO_CMD_UI flag allows you to avoid using MFC's command updating mechanism for all the command items in pop-up menu. The TPMX_NO_SHADOWS flag removes the menu shadow. The TPMX_FORCE_NO_ANIMATION flag causes the menu to appear very quickly without any animation, which is handy when the context menus is activated with a keyboard shortcut.

It is also possible to return an identifier of the clicked command item instead of sending the WM_COMMAND message:

UINT nResultCmdID = 0;
VERIFY(
pPopup->TrackPopupMenu(
TPMX_DO_MESSAGE_LOOP|TPMX_NO_WM_COMMAND, // dwTrackFlags
x,
y,
NULL,
NULL,
NULL,
&nResultCmdID
)
);

The WM_COMMAND message is not sent due to the specified TPMX_NO_WM_COMMAND flag. The nResultCmdID variable is set to zero if pop-up menu is closed without clicking the command item. Otherwise nResultCmdID contains an identifier of the clicked command item.

Prof-UIS allows you to create and/or modify pop-up menus on-the-fly immediately before they appear on the screen. The pop-up menu sends the CExtPopupMenuWnd::g_nMsgPrepareMenu registered windows message only once before the menu tree appears on the screen.

afx_msg LRESULT OnExtMenuPrepare( WPARAM wParam, LPARAM lParam );

ON_REGISTERED_MESSAGE(
CExtPopupMenuWnd::g_nMsgPrepareMenu,
OnExtMenuPrepare
)

LRESULT CMainFrame::OnExtMenuPrepare( WPARAM wParam, LPARAM lParam )
{
lParam;
CExtPopupMenuWnd::MsgPrepareMenuData_t * pData =
reinterpret_cast
< CExtPopupMenuWnd::MsgPrepareMenuData_t * >
( wParam )
ASSERT( pData != NULL ); CExtPopupMenuWnd * pPopup = pData->m_pPopup;
ASSERT( pPopup != NULL );
. . .
// modify the pPopup menu
. . .
return TRUE;
}

If the pop-up menu has been modified, the pData->m_bMenuChanged property should be set to true by the OnExtMenuPrepare() method. If the pData->m_bMenuCanceled is true, pop-up menu tracking will be canceled.

The CExtPopupMenuWnd::g_nMsgPrepareOneMenuLevel registered windows message is also sent by the pop-up menu before it appears on the screen. This message has the same parameters as in case of CExtPopupMenuWnd::g_nMsgPrepareMenu but it is sent for each pop-up menu level.

By handling these registered windows messages, you can implement dynamically generated command lists similar to the most recently used file list (MRU) used in MFC. Just create a pre-defined command item in the menu, find and remove it in the OnExtMenuPrepare() method, and insert new menu items at the position of the removed item. To find a menu item by its command identifier, you can use the CExtPopupMenuWnd::ItemFindPosForCmdID() method. The menu modification code should allow for more than one command identifiers that are replaced:

INT nReplacePos =
pPopup->ItemFindPosForCmdID( ID_OF_ITEM_TO_REMOVE );
for( ; nReplacePos >= 0;
nReplacePos =
pPopup->ItemFindPosForCmdID( ID_OF_ITEM_TO_REMOVE )
)
{
VERIFY( pPopup->ItemRemove(nReplacePos) );
VERIFY(
pPopup->ItemInsert(
ID_OF_NEW_COMMAND_ITEM,
nReplacePos
)
);
. . .
}

The CExtPopupMenuWnd::g_nMsgPopupDrawItem registered message is sent for each menu item and allows you to repaint it. The TPMX_OWNERDRAW_FIXED flag should be specified in the dwTrackFlags parameter when invoking the CExtPopupMenuWnd::TrackPopupMenu() method. Here is the declaration, message map entry and handler method for this notification message:

afx_msg LRESULT OnDrawPopupMenuItem( WPARAM wParam, LPARAM lParam );

ON_REGISTERED_MESSAGE(
CExtPopupMenuWnd::g_nMsgPopupDrawItem,
OnDrawPopupMenuItem
)

LRESULT CMainFrame::OnDrawPopupMenuItem( WPARAM wParam, LPARAM lParam )
{
wParam;
CExtPopupMenuWnd::DRAWITEMDATA * pDrawItemData =
reinterpret_cast
< CExtPopupMenuWnd::DRAWITEMDATA * >
( lParam );
ASSERT( pDrawItemData != NULL );
UINT nCmdID = pDrawItemData->GetCmdID();
if( nCmdID != ID_OWNERDRAW_ITEM )
return 0; // peform default painting
// paint default menu item's background
pDrawItemData->PaintDefaultBk();
CDC & dc = *( (CDC *) *pDrawItemData );
CRect rcItem = LPCRECT(*pDrawItemData);
. . .
// painting code goes here
. . .
return (!0);
}

The CExtPopupMenuWnd::g_nMsgPopupDrawLeftArea registered windows message is sent once for each pop-up sub menu and enables you to repaint its left area. To turn on the left area, invoke the CExtPopupMenuWnd::SetLeftAreaWidth() method and specify its width in pixels. Here is the declaration, message map entry and handler method for this notification message:

afx_msg LRESULT OnDrawPopupLeftArea( WPARAM wParam, LPARAM lParam );

ON_REGISTERED_MESSAGE(
CExtPopupMenuWnd::g_nMsgPopupDrawLeftArea,
OnDrawPopupLeftArea
)

LRESULT CMainFrame::OnDrawPopupLeftArea( WPARAM wParam, LPARAM lParam )
{
wParam;
CExtPopupMenuWnd::DRAWLEFTAREADATA * pDrawLeftAreaData =
reinterpret_cast
< CExtPopupMenuWnd::DRAWLEFTAREADATA * >
( lParam );
ASSERT( pDrawLeftAreaData != NULL );
CDC & dc = *( (CDC *) *pDrawLeftAreaData );
CRect rcItem = LPCRECT(*pDrawLeftAreaData);
. . .
// painting code goes here
. . .
return (!0);
}