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:
- Creation.
- IContextMenu::QueryContextMenu. This initializes the context menu. During this call, the context menu decides which items appear in it, based on the flags you pass.
- Display the menu or otherwise select a command to execute, using IContextMenu::GetCommandString, IContextMenu2::HandleMenuMsg and IContextMenu3::HandleMenuMsg2 to faciliate the user interaction.
- IContextMenu::InvokeCommand. This executes the command.
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.]
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.
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"?
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.
I prefer the dictionary style myself. I think it has to do with which half of the brain you think with.
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.
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.
I know zero about Windows Forms.
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.
I know zero too about Windows Forms!
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.
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.
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?
Um, that’s what the category feeds are for.
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?
IContextMenu のホスト方法 – Shell
PingBack from http://blogwell.wordpress.com/2007/06/05/implementing-sent-to-mail-recipient-in-your-application/
PingBack from http://blog-well.com/2007/06/05/implementing-sent-to-mail-recipient-in-your-application/