How to host an IContextMenu, part 1 – Initial foray


Most documentation describes how to plug into the shell context menu structure and be a context menu provider. If you read the documentation from the other side, then you also see how to host the context menu. (This is the first of an eleven-part series with three digressions. Yes, eleven parts—sorry for all you folks who are in it just for the history articles. I'll try to toss in an occasional amusing diversion.)

The usage pattern for an IContextMenu is as follows:

The details of this are explained in Creating Context MenuHandlers from the point of view of the IContextMenu implementor.

The Shell first calls IContextMenu::QueryContextMenu. It passes in an HMENU handle that the method can use to add items to the context menu. If the user selects one of the commands, IContextMenu::GetCommandString is called to retrieve the Help string that will be displayed on the Microsoft Windows Explorer status bar. If the user clicks one of the handler's items, the Shell calls IContextMenu::InvokeCommand. The handler can then execute the appropriate command.

Read it from the other side to see what it says you need to do as the IContextMenu host:

The IContextMenu host first calls IContextMenu::QueryContextMenu. It passes in an HMENU handle that the method can use to add items to the context menu. If the user selects one of the commands, IContextMenu::GetCommandString is called to retrieve the Help string that will be displayed on the host's status bar. If the user clicks one of the handler's items, the IContextMenu host calls IContextMenu::InvokeCommand. The handler can then execute the appropriate command.

Exploring the consequences of this new interpretation of the context menu documentation will be our focus for the next few weeks.

Okay, let's get started. We begin, as always, with our scratch program. I'm going to assume you're already familiar with the shell namespace and pidls so I can focus on the context menu part of the issue.

#include <shlobj.h>

HRESULT GetUIObjectOfFile(HWND hwnd, LPCWSTR pszPath, REFIID riid, void **ppv)
{
  *ppv = NULL;
  HRESULT hr;
  LPITEMIDLIST pidl;
  SFGAOF sfgao;
  if (SUCCEEDED(hr = SHParseDisplayName(pszPath, NULL, &pidl, 0, &sfgao))) {
    IShellFolder *psf;
    LPCITEMIDLIST pidlChild;
    if (SUCCEEDED(hr = SHBindToParent(pidl, IID_IShellFolder,
                                      (void**)&psf, &pidlChild))) {
      hr = psf->GetUIObjectOf(hwnd, 1, &pidlChild, riid, NULL, ppv);
      psf->Release();
    }
    CoTaskMemFree(pidl);
  }
  return hr;
}

This simple function takes a path and gets a shell UI object from it. We convert the path to a pidl with SHParseDisplayName, then bind to the pidl's parent with SHBindToParent, then ask the parent for the UI object of the child with IShellFolder::GetUIObjectOf. I'm assuming you've had enough experience with the namespace that this is ho-hum.

(The helper functions SHParseDisplayName and SHBindToParent don't do anything you couldn't have done yourself. They just save you some typing. Once you start using the shell namespace for any nontrivial amount of time, you build up a library of little functions like these.)

For our first pass, all we're going to do is invoke the "Play" verb on the file when the user right-clicks. (Why right-click? Because a future version of this program will display a context menu.)

#define SCRATCH_QCM_FIRST 1
#define SCRATCH_QCM_LAST  0x7FFF

void OnContextMenu(HWND hwnd, HWND hwndContext, UINT xPos, UINT yPos)
{
  IContextMenu *pcm;
  if (SUCCEEDED(GetUIObjectOfFile(hwnd, L"C:\\Windows\\clock.avi",
                   IID_IContextMenu, (void**)&pcm))) {
    HMENU hmenu = CreatePopupMenu();
    if (hmenu) {
      if (SUCCEEDED(pcm->QueryContextMenu(hmenu, 0,
                             SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST,
                             CMF_NORMAL))) {
        CMINVOKECOMMANDINFO info = { 0 };
        info.cbSize = sizeof(info);
        info.hwnd = hwnd;
        info.lpVerb = "play";
        pcm->InvokeCommand(&info);
      }
      DestroyMenu(hmenu);
    }
    pcm->Release();
  }
}

    HANDLE_MSG(hwnd, WM_CONTEXTMENU, OnContextMenu);

As noted in the checklist above, first we create the IContextMenu, then initialize it by calling IContextMenu::QueryContextMenu. Notice that even though we don't intend to display the menu, we still have to create a popup menu because IContextMenu::QueryContextMenu requires on. We don't actually display the resulting menu, however; instead of asking the user to pick an item from the menu, we make the choice for the user and select "Play", filling in the CMINVOKECOMMANDINFO structure and invoking it.

But how did we know that the correct verb was "Play"? In this case, we knew because we hard-coded the file to "clock.avi" and we knew that AVI files have a "Play" verb. But of course that doesn't work in general. Before getting to invoking the default verb, let's first take the easier step of asking the user what verb to invoke. That exercise will actually distract us for a while, but we'll come back to the issue of the default verb afterwards.

If the code above is all you really wanted (invoking a fixed verb on a file), then you didn't need to go through all the context menu stuff. The code above is equivalent to calling the ShellExecuteEx function, passing the SEE_MASK_INVOKEIDLIST flag to indicate that you want the invoke to go through the IContextMenu.

[Typo fixed 25 September.]

Comments (20)
  1. Moi says:

    Nice article, but it shouldn’t be needed. The MSDN documentation really shouldn’t require people to "read the contract from the other side". I can only assume that the people who write the documentation think that everyone still uses RPN calculators.

  2. Raymond Chen says:

    All interfaces can be read from both sides. Most people focus on the implementation side, but there’s also the consumer side.

    How are you suggesting that the documentation should be changed? Should every interface say, "You can either implement or call this interface"?

  3. Andy says:

    MS programming docs are interesting. Some authors seem to write "cookbook" style. There is useful info in the remarks section and sometimes a link to sample code.

    Others are far more dictionary style. A terse description of the function or API with almost no clue as to how to use it to get useful work done.

    The latter requires reading between the lines and from the other side. Too much extrapolation in my opinion.

  4. Raymond Chen says:

    I prefer the dictionary style myself. I think it has to do with which half of the brain you think with.

  5. Jeff Parker says:

    A quick question in this 11 part series will you be demonstrating the .net implementations?

    System.Windows.Forms.ContextMenu

    Just curious, but don’t let me disuade you from where you are going. I just haven’t used C++ in at least 3 years since C# came out. And yeah I know there are other .net blogs but none of them compair to the Raymond and Larry Blogs for in depth detail and knowledge.

  6. Raymond: Dictionary style only works if you have extensive sample code, the source code of the API implementation, or a large number of reference books to work from.

    That’s the thing about Index knowledge; it lets you find what you’re looking for. It doesn’t, however, teach concepts or ideas.

    For an example of an API which desperately needs better sample code and some extensive real-world examples, see Uniscribe. While Uniscribe straddles the line between cookbook and dictionary in its documentation, it still leaves lots of questions regarding best practices et al unanswered.

    I guess what it all boils down to is this:

    Dictionary-style doco is best when one already knows the subject at hand. Cookbook style is best when one needs to learn the subject. Good documentation includes both.

  7. Raymond Chen says:

    I know zero about Windows Forms.

  8. mschaef says:

    The thing that bothers me about reading the documentation "from the other side", is that that’s basically what Microsoft has to do to implement the Windows API in the first place. Given all the trouble that Microsoft has had ensuring compatibility over the years, that doesn’t give me much confidence in my own ability to produce a reasonable context menu host (or whatever). Obviously, ISV software doesn’t have quite the same compatibility requirements as Windows itself, but given the lower resources most ISV’s can bring to bear on the problem, implementing the API from the other side seems like it might be a fairly costly move.

  9. I know zero too about Windows Forms!

  10. The MSDN documentation for most standard interfaces honors both sides by having "When to Implement", "When to Use", "Notes to Callers" and "Notes to Implementers" sections.

    Most interfaces, however, either have just a few implementors or they have just a few callers. The one more exclusive side of an interface becomes "the other side" of that interface. This side is harder to join because everybody tests against the few well known instances of this side. If somebody implements IContextMenu, he or she will most likely only test against the Windows Shell, despite the fact that "Context Menu" is a quite general term. If the code runs successfully in the shell, it is considered correct.

    This has been a major problem for unmanaged component software.

    In [HKLMSOFTWAREMicrosoftWindowsCurrentVersionShellCompatibilityObjects] there are strange flags like "CTXMENU_LIMITEDQI", indicating that there are implementations of IContextMenu which may even get a menu host in trouble if he QI’s the object differently than a previous version of the shell.

  11. asdf says:

    For some more sample source code, I love ContextMenu:

    http://www.maddogsw.com/cmdutils/

    It even has code to parse the menu to output text instead of displaying a GUI menu.

  12. Eric TF Bat says:

    For the benefit of the people who just want the history and the anecdotes, why not mark your articles with categories and make it possible to view entries by categories? Does the blogging software have that kind of capability?

  13. Raymond Chen says:

    Um, that’s what the category feeds are for.

  14. name says:

    The thing that still bugs me about reading specs from the other side is that it won’t always work.

    Windows’s implementation has to be compatible with buggy applications, and may contain workarounds you won’t think of. Or Windows may have bugs or quirks that applications depend on. Or it may handle details of a correct implementation that aren’t obvious in the spec (e.g., ShellExecute expands environment strings). And when specs change, you may have to track them.

    Is the strategy of reading a contract from the other side successfully used on a variety of specs in real applications? If not, why not? If so, does it cause the implementors endless grief, or what?

  15. Cooney says:

    Is the strategy of reading a contract from the other side successfully used on a variety of specs in real applications? If not, why not? If so, does it cause the implementors endless grief, or what?

    Probably not. If it were, it’d be more akin to a standard – implementations are judged by their compliance to a standard, and variations are treated as bugs in the code.

    Windows is different – it is the defacto standard, and is authoritative, more so than any written spec. Variations between the code and the spec are treated as bugs in the spec (provided that someone somewhere depends on the behavior).

    I think the best way to view this is to look at the docs as a starting point, and then resolve any questions by testing the code to see what it does (which is how we get bug-dependency in the first place).

  16. Mike Dunn says:

    Is the strategy of reading a contract from the other side successfully used on a variety of specs in real applications?

    I imagine that reading the context menu shell extension spec from the other side is done often, since there are many Explorer shell replacements out there, and a file/dir context menu is a pretty big part of the shell.

    Other apps like WndTabs for VC 6 do it as well (r-click a tab and you can get the Explorer context menu for a source file).

  17. IContextMenu のホスト方法 – Shell

Comments are closed.