How to host an IContextMenu, part 11 - Composite extensions - composition

Okay, now that we have two context menu handlers we want to compose
(namely, the "real" one from the shell namespace and a "fake" one
that contains bonus commands we want to add), we can use merge them
together by means of a composite context menu handler.

The kernel of the composite context menu is to multiplex multiple
context menus onto a single context menu handler, using the menu identifer
offsets to route the commands.

The local structure CONTEXTMENUINFO contains
information about each of the context menus that are part of
our composite. We need to have the context menu pointer itself,
as well as the number of menu identifiers consumed by that
context menu by its
IContextMenu::QueryContextMenu handler.
We'll see why as we implement this class.

Since a C++ constructor cannot fail, there are various
conventions for how one handles failure during construction.
One convention, which I use here, is to put the bulk of
the work in an Initialize method, which can
return an appropriate error code if the initialization fails.

(Note that here I am assuming a non-throwing new operator.)

Our initialization function allocates a bunch of
CONTEXTMENUINFO structures and copies the
IContextMenu pointers (and AddRefs them)
for safekeeping. (Note that the m_ccmi member is
not set until after we know that the memory allocation succeeded.)

We ask each contained context menu
in turn to add its commands to the context menu. Here is
where you see one of the reasons for the return value of the
IContextMenu::QueryContextMenu method.
By telling tells the container how many menu identifiers
you used, the container knows how many are left for others.
The container then returns the total number of menu identifiers
consumed by all of the context menus.

Another reason for the return value of the
IContextMenu::QueryContextMenu method
is seen in the next helper method:

This method takes a menu offset and figures out which
of the contained context menus it belongs to,
using the return value from
IContextMenu::QueryContextMenu to decide
how to divide up the identifier space.
The pidCmd parameter is in/out.
On entry, it's the menu offset for the composite
context menu; on exit, it's the menu offset for
the contained context menu that is returned via
the ppcmi parameter.

The IContextMenu::InvokeCommand is probably
the most complicated, since it needs to support the
four different ways of dispatching the command.

After some preliminary munging to find the command identifier,
we dispatch the invocation in three steps.

First, if the command is being dispatched as a string, then
this is the easiest case. We loop through all the contained
context menus asking each one if it recognizes the command.
Once one does, we are done. And if nobody does, then we
shrug and say we don't know either.

Second, if the command being dispatched is an ordinal,
we ask ReduceOrdinal to figure out which contained
context menu handler it belongs to.

Third, we rewrite the CMINVOKECOMMANDINFO structure
so it is suitable for use by the contained context menu handler.
This means changing the lpVerb member and possibly the
lpVerbW member to contain the new menu offset relative
to the contained context menu handler rather than being relative
to the container.
This is complicated slightly by the fact that the Unicode verb
lpVerbW might not exist. We hide that behind a
pszVerbWFake local variable which stands in if
there is no genuine lpVerbW.

Okay, now that you see the basic idea behind distributing the
method calls to the appropriate contained context menu, the
rest should be comparatively easy.

The GetCommandString method follows the same
three-step pattern as InvokeCommand.

First, dispatch any string-based commands by calling each
contained context menu handler until somebody accepts it.
If nobody does, then reject the command.
(Note the special handling of GCS_VALIDATE,
which expects S_FALSE rather than an error code.)

Second, if the command is specified by ordinal, ask
ReduceOrdinal to figure out which contained
context menu handler it belongs to.

This helper function takes an IContextMenu
interface pointer and tries to invoke
IContextMenu3::HandleMenuMsg2; if that fails,
then it tries IContextMenu2::HandleMenuMsg; and
if that also fails, then it gives up.

And the IContextMenu3::HandleMenuMsg2 method
merely walks through the list of context menu handlers,
asking each one whether it wishes to handle the command,
stopping when one finally does.

Armed with this composite menu class, we can show it off
in our sample program by compositing the "real" context menu
with our CTopContextMenu, thereby showing
how you can combine multiple context menus into one big
context menu.

This function builds the composite by creating the two
contained context menu handlers, then creating a composite
context menu that contains both of them. We can use this
function by making the same one-line tweak to the
OnContextMenu function that we tweaked last time:

Notice that with this composite context menu, the menu help text
that we update in our window title tracks across both the
original file context menu and our "Top" context menu.
Commands from either half are also invoked successfully.

The value of this approach over the method from
part 9
is that you no longer have to coordinate the customization of
the context menu between two pieces of code. Under the previous
technique, you had to make sure that the code that updated the
menu help text was in sync with the code that added the custom
commands.

Under the new method, all the customizations are kept in one
place (in the "Top" context menu which is inside the
composite context menu), so that the window procedure doesn't need
to know what customizations have taken place.
This becomes more valuable if there are multiple points at which
context menus are displayed, some uncustomized, others customized
in different ways. Centralizing the knowledge of the customizations
simplifies the design.

Okay, I think that's enough on context menus for now.
I hope you've gotten a better understanding of how they work,
how you can exploit them, and most importantly, how you can
perform meta-operations on them with techniques like composition.

There are still some
other things you can do with context menus, but I'm going to leave you
to experiment with them on your own. For example, you can use
the IContextMenu::GetCommandString method to walk
the menu and obtain a language-independent command mame
for each item.
This is handy if you want to, say, remove the "delete" option:
You can look for the command whose language-independent name
is "delete". This name does not change when the user changes
languages; it will always be in English.

As we've noticed before, you need to be aware
that many context menu handlers don't implement
the IContextMenu::GetCommandString method fully,
so there will likely be commands that you simply cannot get
a name for. Them's the breaks.