3

I have created a shell extension for windows with COM, however I seem to fail to properly match the ids of items I add in the overload of IContextMenu::QueryContextMenu with what I receive in the overload of IContextMenu::InvokeCommand. In my code I use InsertMenu and InsertMenuItem (as far as I understood they do the same, but the latter has some more features?). However I'm not sure which arguments passed to InsertMenu/InsertMenuItem correspond to what I must be looking for in LPCMINVOKECOMMANDINFO::lpVerb. I need some way to easily know that when I add items x, y, z to a context menu, I can then know exactly which one of x, y or z has been clicked.

EDIT: It seems that the verb equals the number from top to bottom of the item in the current menu/submenu. However I have two sub-menus each with x amount of items, so they have the same IDs of 1,2,3. How do I set custom IDs or something?

ulak blade
  • 2,515
  • 5
  • 37
  • 81
  • 1
    Don't ask for a manual, it has been written already. Show your code. – Hans Passant Oct 16 '15 at 14:38
  • Where has it been written? – ulak blade Oct 16 '15 at 14:43
  • @HansPassant I can't seem to find a manual that explains this very well, there is only an online tutorial that adds just a single verb and you always know it's the one – ulak blade Oct 16 '15 at 15:45
  • Even if you add only one menu item shell can call your InvokeCommand with external ID. So you MUST check command ID even in case of single menu item. – Denis Anisimov Oct 16 '15 at 17:07
  • 1
    I suspect that `lpVerb` you are getting may be `MAKEINTRESOURCE(yourCommandId)`. Check with `IS_INTRESOURCE`. Alternatively, it could be whatever verb you associated with the command via your implementation of `IContextMenu::GetCommandString(GCS_VERB{A|W})` – Igor Tandetnik Oct 16 '15 at 19:59
  • 1
    This is documented here: https://msdn.microsoft.com/en-us/library/windows/desktop/hh127443.aspx . In this doc, a custom command 'IDM_DISPLAY' is fully implemented as an example. – Simon Mourier Oct 19 '15 at 15:20
  • @shortage_radeon Please show your `QueryContextMenu` and your `InvokeCommand` functions. – Constantin Oct 22 '15 at 09:37

1 Answers1

3

Firstly you should define an enum that holds the command IDs for your menu items, e.g.

enum {
    CMDID_FIRST = 0,

    CMDID_DOSOMETHING = CMDID_FIRST,
    CMDID_DOSOMETHINGELSE,

    CMDID_LAST,
};

These ID values need to start from 0.

In your IContextMenu::QueryContextMenu implementation:

  • when you add your menu items you need to give each of them an ID by setting the MIIM_ID flag in the MENUITEMINFO.fMask field, and setting the MENUITEMINFO.wID value.

  • give each of your menu items an ID derived from its command ID as defined above, plus the value of idCmdFirst which is passed into QueryContextMenu. E.g. the "Do Something" menu item would have MENUITEMINFO.wID set to idCmdFirst + CMDID_DOSOMETHING, and "Do Something Else" would have MENUITEMINFO.wID set to idCmdFirst + CMDID_DOSOMETHINGELSE.

  • the return value from QueryContextMenu needs to be MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, x) where x is the ID of the highest-numbered item you added plus 1 (alternatively, if all items were sequentially numbered, the total number of items). Basically, you're telling the host which menu item ID values are now in use so that no other context menu extensions add items that clash with yours. In the above example, you'd return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, CMDID_LAST).

In IContextMenu::InvokeCommand:

  • test if lpVerb (or lpVerbW) is an integer value using the IS_INTRESOURCE macro.
  • if so, the command ID can be found in the low word. E.g, if the user selected "Do Something Else", you would find that LOWORD(lpVerb) == CMDID_DOSOMETHINGELSE.
Jonathan Potter
  • 36,172
  • 4
  • 64
  • 79
  • Do you know why there's a certain asymmetry between `QueryContextMenu` and `InvokeCommand`, in that you add the `idCmdFirst` offset in the first method, and yet you don't subtract it in the second? –  Jun 02 '20 at 21:07
  • @loop123123 I'm guessing that the host subtracts the value when invoking your command as a convenience to you. Otherwise, you'd need to store `idCmdFirst` somewhere (since it's not passed as part of the invoke). When adding you need to do the maths because you're the one actually creating the menu item, and the ID is an integral part of the menu item. – Jonathan Potter Jun 02 '20 at 21:10
  • Cool, thanks. One thing I'd recommend adding is that if you don't process the command in `InvokeCommand`, you need to return `E_FAIL`. Otherwise, you totally mess up other context menu commands. I just learned that the hard way. –  Jun 05 '20 at 00:12
  • @loop123123 Yes this is in the documentation. *If the verb, specified either by a canonical verb name or the command ID is not recognized by the context menu handler, it must return a failure (E_FAIL) so that the verb can be passed on to other context menu handlers that might implement it.* – Jonathan Potter Jun 05 '20 at 01:07
  • Would you be willing to provide any feedback on my approach here? https://stackoverflow.com/a/62374937/13366655 –  Jun 14 '20 at 16:07