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
How to Get the Starting/Base Address of a Process in C++
C++ Conversion Operator for Converting to Function Pointer
Handling Ssl_Shutdown Correctly
Why C++ Doesn't Support Named Parameter
Direct C Function Call Using Gcc's Inline Assembly
Observable Behavior and Undefined Behavior -- What Happens If I Don't Call a Destructor
What Rules Does Compiler Have to Follow When Dealing with Volatile Memory Locations
Add Elements to a Vector During Range-Based Loop C++11
Xcode 3.2.1 and C++ String Fails!
How to Write Video File in Opencv 2.4.3
How to Embed the Gnu Octave in C/C++ Program
Is It Illegal to Invoke a Std::Function<Void(Args...)> Under the Standard
What Does Exactly the Warning Mean About Hidden Symbol Being Referenced by Dso
How to Combine Std::Bind(), Variadic Templates, and Perfect Forwarding