Dynamic Menu Using Mfc

Dynamic menu using mfc

You can create a CMenu object dynamically like this:

CMenu *menu = new CMenu;
menu->CreatePopupMenu();
// Add items to the menu
menu->AppendMenu(MF_STRING, menuItemID, "Text");
...

Then add this sub-menu to your main menu:

wnd->GetMenu()->AppendMenu(MF_POPUP, (UINT_PTR)menu->m_hMenu, "Menu Name");

As for the message map, assuming all your menu item IDs are within a certain range, you can use ON_COMMAND_RANGE to map the entire range to a single function. This function will receive the ID as a parameter, and within the function, you can perform different operations based on the ID.

MFC menus Open tabs dynamic menu

The list of open windows is added to the "Window" menu by the MFC framework, in the application's main window handler for the WM_INITMENUPOPUP command. (Actually, the framework adds the items to all menus that already contain any command with an ID between AFX_IDM_WINDOW_FIRST and AFX_IDM_WINDOW_LAST – which includes the "Cascasde," "Tile..." and "Arrange Icons" default values.)

You can remove these items by adding the ON_WM_INITMENUPOPUP() handler to your frame window's message map and overriding that frame's OnInitMenuPopup() member function.

Assuming your main frame is derived from the CMDIFrameWnd class, this override would look something like the following:

void MyFrameWnd::OnInitMenuPopup(CMenu *pPopupMenu, UINT nIndex, BOOL bSysMenu)
{
CMDIFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);// Call base class FIRST
// <Insert any other code for your override>
UINT commID = AFX_IDM_FIRST_MDICHILD; // MFC gives the first item this ID
BOOL hadID;
do {
hadID = pPopupMenu->RemoveMenu(commID, MF_BYCOMMAND);
++commID;
} while (hadID);
return;
}

The declaration of the function in your class should properly have the afx_msg attribute, as shown below, although this is generally defined as 'nothing' in the recent MFC versions:

class MyFrameWnd : public CMDIFrameWnd
{
//...
protected:
afx_msg void OnInitMenuPopup(CMenu *pPopupMenu, UINT nIndex, BOOL bSysMenu);
//...

You could also reduce the removal 'loop' code to the following single line, if you prefer such compressed code:

    for (UINT commID = AFX_IDM_FIRST_MDICHILD; Menu->RemoveMenu(commID, MF_BYCOMMAND); ++commID) ; // Empty loop

Note that this removal process will leave a 'separator' at the bottom of your application's "Window" menu; removing this as well would involve a bit more work, as that separator doesn't have a command ID – so you would need to determine its position (index) in the menu and call the RemoveMenu() function using that index and the MF_BYPOSITION flag as the second argument.

Using the code from the first snippet, and assuming that separator is at the very end of your menu, this could be achieved by adding the following code after the removal loop:

    if (commID > AFX_IDM_FIRST_MDICHILD + 1) { // We removed at least one item, so ...
int nItems = Menu->GetMenuItemCount(); // ... remove the separator,
pPopupMenu->RemoveMenu(nItems - 1, MF_BYPOSITION); // assuming it's the last item!
}

Creating modern style dynamic menu in Windows

I discovered the solution, but don't really understand what was happening. The solution is to prevent calling DestroyMenu on subMenu's destructor at the end of the function. This is done by either calling subMenu.Detach(), or making subMenu a pointer to CMenu.

What I don't understand is why is DestroyMenu turning new style menu to old style. I would expect that the menu is either destroyed and not shown, or copied in SetMenuItemInfo and so its style preserved. Whoever provides an answer to this one gets my vote :)

Also, I would like to know if I'm producing a resource leak by calling Detach here, or is my dynamic sub-menu destroyed along with the main menu. Points await the one who provides an answer.

Dynamic menus in MFC

If you're calling GetMenu from within a window class derived from CWnd, you'll be calling CWnd::GetMenu and it will not require a window handle. If you're calling it from anywhere else you will get ::GetMenu(HWND) and you will need to pass a window handle. You can get the handle from any CWnd object with its m_hWnd member or by calling GetSafeHwnd() on it.

Handling dynamically populated CMenu messages

Look at this answer:

https://stackoverflow.com/a/3673672/2287576

As for the message map, assuming all your menu item IDs are within a certain range, you can use ON_COMMAND_RANGE to map the entire range to a single function. This function will receive the ID as a parameter, and within the function, you can perform different operations based on the ID.

Assuming you can set aside a range of ID values this method will work.

How do I create a dynamic menu for a ribbon button?

The ribbon seems to only use the hMenu as a template to build it's own structure from it, so modifying the hMenu is vain. Better work with the existing Ribbon Menu Button:

    pBtn->RemoveAllSubItems(); // add a dummy hMenu when creating the menu button in CMainFrame!

std::auto_ptr<CMFCRibbonButtonEx> apBtn3(new CMFCRibbonButtonEx(ID_DYNAMIC_MENU_ITEM_3, "Item 3", -1, -1, true));
pBtn->AddSubItem(apBtn.release());

std::auto_ptr<CMFCRibbonButtonEx> apBtn4(new CMFCRibbonButtonEx(ID_DYNAMIC_MENU_ITEM_4, "Item 4", -1, -1, true));
pBtn->AddSubItem(apBtn.release());

But make sure to update the menu at the right place in your code. Changing the menu in CMyView::OnUpdate() proved to be not such a good idea (see here). If you need to modify the menu when opening a mdi document, consider OnInitialUpdate(). I haven't tried OnCmdMsg() yet.

Maybe its sufficient to get pBtn via CMFCRibbonBar::FindByID() but maybe its the right thing to iterate through CMFCRibbonBar::GetElementsByID and change each menu button you find (i.e. to also modify the quick access toolbar?)... I found the documentation is not very specific about that but modifying via ´FindByID´ seems to be sufficient in my code.

If you have any further revelations about dynamic ribbon menus, please leave me a comment.

use command range for dynamic menu

You've added the command range to the message map for the tree control, but you're not using the tree control as the owner window for the menu.

CWnd* pWndPopupOwner = this;
while (pWndPopupOwner->GetStyle() & WS_CHILD)
pWndPopupOwner = pWndPopupOwner->GetParent();

This will find the top-level window and use it as the popup's owner, which means the WM_COMMAND messages from the popup will go to the top-level window rather than the tree. You need to add the command range to that window's message map.

Alternatively, you can use the TPM_RETURNCMD flag with TrackPopupMenu which will return the selected command ID, and then invoke your OnNewMenu() function yourself:

int iID = menu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, 
point.x, point.y, pWndPopupOwner);
if (iID) OnNewMenu(iID);


Related Topics



Leave a reply



Submit