8

I'm trying to disable/gray out menu items using the CMenu::EnableMenuItem() method.

I have a CMenu* variable pMenu which references the top menu of the dialog. I can get the submenu using pMenu->GetSubMenu(int) and using submenu->GetMenuStringA(), verify the names of the submenus/menu items that I get back. I'm having trouble with the EnableMenuItem() method though. Let's say theres a File menu. Within it there are New and Open popup menus and Import, Close, and Close All menu items. The New and Open have submenu items. (e.g New->Document) Using submenu->EnableMenuItem([position of submenu/menuitem], MF_BYPOSITION | MF_GRAYED); I can disable New or Open, however the function fails for Import, Close and Close All, as well as all the menu items with New and Open.

Note: When I say EnableMenuItem() fails, I don't mean that it returns -1. It returns the previous status, but the menu doesn't become disabled or grayed out.

In the MSDN documentation for EnableMenuItem(): http://msdn.microsoft.com/en-us/library/h62wh3y1.aspx it claims that this will work for both pop up and standard menu items. It only seems to work for pop up ones, though.

Amre
  • 1,630
  • 8
  • 29
  • 41

2 Answers2

7

MFC has another scheme for enabling/disabling menu items and that scheme is undoing what you are doing. To work within the MFC scheme you add message handlers ON_UPDATE_CMD_UI as described here:

http://msdn.microsoft.com/en-us/library/6kc4d8fh.aspx

ScottMcP-MVP
  • 10,337
  • 2
  • 15
  • 15
  • The problem is, I doing all this from an external application. I'm using the handle of the main window to get it's menu, then I use GetSubMenu to get it's submenus, I don't have access to the CCmdUI object. – Amre Jan 19 '15 at 14:41
  • @Amre then you're stuck. The application you're trying to control is resetting the enabled/disabled state constantly through its own CCmdUI object, and any change you make will be reverted in milliseconds. – Mark Ransom Jan 19 '15 at 17:44
  • Essentially what I'm trying to accomplish with this is sending the oncommand message to the button. I'm trying create an app that I can use to test the security of my app, against someone sending the oncommand or bn_clicked message to a control and accessing something they're not supposed to. – Amre Jan 19 '15 at 18:07
  • I realized that I never really gave an update to this. In the end, I didn't bother with trying to enable grayed out buttons or menu items and just invoked them while they were disabled. I did this for buttons like this: CWnd* selCWnd = //get CWnd reference ::SendMessage(selCWnd->m_hWnd, WM_LBUTTONDOWN, 0, 0); ::SendMessage(selCWnd->m_hWnd, WM_LBUTTONUP, 0, 0); So I found that I could just send a left button down, followed by a left button up command, and that mimicked clicking it and it worked! – Amre May 08 '17 at 13:44
  • For menu items I did this by using PostMessage and sending the WM_COMMAND msg to menu item using the id. I.E.: ::PostMessage(m_HWNDSel, WM_COMMAND, MAKEWPARAM(id, 0), 0); where id is the menu item ID. I was able to get these IDs by getting a reference to the PMenu that referenced the menu bar, then I iterated through all the submenu and got the menu item IDs by using pMenu->GetMenuItemID() – Amre May 08 '17 at 13:45
3

A stated by ScottMcP-MVP MFC does the menu configuration in the ON_UPDATE_COMMAND_UI handler : When a user of your application pulls down a menu, each menu item needs to know whether it should be displayed as enabled or disabled. The target of a menu command provides this information by implementing an ON_UPDATE_COMMAND_UI handler. For each of the command user-interface objects in your application, use the Properties window to create a message-map entry and function prototype for each handler.

When the menu is pulled down, the framework searches for and calls each ON_UPDATE_COMMAND_UI handler, each handler calls CCmdUI member functions such as Enable and Check, and the framework then appropriately displays each menu item.

That means that you have to store in your own classes the expected state for the menu item that can be checked/unchecked. You will have to put one ON_UPDATE_COMMAND_UI macro per menu element near the ON_COMMAND macro, and that element will refer a function receiving the CCmdUi object that you can modify to your needs. But as you are using MFC, you normally do not do that by hand but just use the properties of the windows containing the menu.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • 1
    I understand what you're saying, but that's not really what I'm trying to do. I'm not trying to enable/disable the items with the application. I'm calling all these methods within an external application. – Amre Jan 19 '15 at 15:16
  • 2
    You will definitively not be able to do that ! You should instead send custom messages to the MFC application telling it that some menu items should be grayed. And then inside the MFC application you will be able to use the ON_UPDATE_COMMAND_UI handler to enable/disable them. I cannot imagine another way for a MFC application. – Serge Ballesta Jan 19 '15 at 15:35
  • Is there any way to call the oncommand or Bn_clicked message handler if I know the id of the menuitem? I have tried using sendmessage and postmessage passing in the handle of the main window and BN_CLICKED and WM_COMMAND but that doesn't seem to work. – Amre Jan 19 '15 at 17:59
  • 1
    @Amre You can't trigger those MFC events from outside the application, because they are generated by the MFC framework. – Serge Ballesta Jan 19 '15 at 18:15
  • I realized that I never really gave an update to this. In the end, I didn't bother with trying to enable grayed out buttons or menu items and just invoked them while they were disabled. I did this for buttons like this: CWnd* selCWnd = //get CWnd reference ::SendMessage(selCWnd->m_hWnd, WM_LBUTTONDOWN, 0, 0); ::SendMessage(selCWnd->m_hWnd, WM_LBUTTONUP, 0, 0); So I found that I could just send a left button down, followed by a left button up command, and that mimicked clicking it and it worked! – Amre May 08 '17 at 13:41
  • For menu items I did this by using PostMessage and sending the WM_COMMAND msg to menu item using the id. I.E.: ::PostMessage(m_HWNDSel, WM_COMMAND, MAKEWPARAM(id, 0), 0); where id is the menu item ID. I was able to get these IDs by getting a reference to the PMenu that referenced the menu bar, then I iterated through all the submenu and got the menu item IDs by using pMenu->GetMenuItemID() – Amre May 08 '17 at 13:44