Socially Adept Menus

I call these menus socially adept because they're good at mixing. OK, bad pun.

The next phase in integrating the object's user interface with the container's is creating a mixed, or shared, menu that the container displays in its frame window. This menu is composed of pieces from the normal menus of both container and object. In a nutshell, the container retains possession of menus that have to do with the document. It provides the File menu, the Window menu (if the container supports MDI), and any other menus that pertain to document and container functions (such as Patron's Page menu). The object provides the Edit menu and the Help menu and can add any other menu necessary to manipulate the object while it is activated in place. (Examples are Cosmo's Color and Line menus.) This makes sense, but the question is how to take two sets of menus and merge them into one programmatically, as shown in Figure 22-8.

Figure 22-8.

How container and server menus can merge to create one shared menu.

How this works (and it does work) is that both container and object control three menu groups, and each group can have as many individual pop-up menus as desired. The container controls groups named File, Container, and Window; the object has groups named Edit, Object, and Help. The shared menu is the alternating combination of these groups: File, Edit, Container, Object, Window, and Help, as shown in Figure 22-8. (The Color and Line menus are both in the Object group.)

The object initiates the process for building this menu by creating a new, blank menu through the Windows function CreateMenu. It passes this menu to the container's IOleInPlaceFrame::InsertMenus function along with a pointer to a structure named OLEMENUGROUPWIDTHS, which is simply an array of six integers. Inside InsertMenus, the container calls the appropriate Windows API function, either AppendMenu or InsertMenu, to build its half of the menu. After Patron executes InsertMenus, for example, the menu will appear as follows:

On return from InsertMenus, the object inserts its own menu items for each of its groups between those contributed by the container. The container will have stored the width—the number of items—of each of its groups in the OLEMENUGROUPWIDTHS array. The elements at indexes 0, 2, and 4 contain the widths of the File, Container, and Window groups, respectively. In the Patron example above, each array element would contain the value 1.

The object now inserts its own groups between those of the container by using the Windows function InsertMenu, carefully controlling the position of each new item. The object inserts each menu item in its Edit group after the File group but before the Container group, using the width of the File group as the base offset for items in the Edit group. The base offset of the items in the Object group is the width of the File, Container, and Edit groups combined. Likewise, the starting offset for the Help group is the combined widths of all the other groups.

The object completes the OLEMENUGROUPWIDTHS array by storing the number of menus in its groups in array elements 1, 3, and 5. It then creates a menu descriptor by calling OleCreateMenuDescriptor, passing the new menu's handle and the width array. The object then passes that descriptor, the new menu handle, and a window handle to receive messages generated from the object's menus to IOleInPlaceFrame::SetMenu. At that time, the container displays the shared menu, as shown in Figure 22-9, and passes all the

Figure 22-9.

Patron displaying a menu it shares with Cosmo.

information to OleSetMenuDescriptor, which is the key to making all this work. OleSetMenuDescriptor creates a message hook on the container's main window so that it can watch the messages generated from this shared menu (WM_COMMAND, WM_MENUSELECT, and so on) and redirect them to the object's window handle. OLE determines who owns which menu by checking the position of the menu generating the message against the group widths array stored in the descriptor. No magic!