Activate Objects and Add the Object Verb Menu

In the introduction to this chapter, we described how the activation of an object is a primary feature of OLE Documents. Activation is what separates an embedded or a linked object from a static one. To activate an object means to tell it to execute a verb through IOleObject::DoVerb. This might show a user interface in which the user can edit the object, or it might play a sound or a video clip. This function does whatever is appropriate for the object itself. In Patron, all activation goes through CTenant::Activate:8


BOOL CTenant::Activate(LONG iVerb)
{
RECT rc, rcH;
CHourglass *pHour;
SIZEL szl;

[Just beep for static objects.]

RECTFROMRECTL(rc, m_rcl);
RectConvertMappings(&rc, NULL, TRUE);
XformRectInPixelsToHimetric(NULL, &rc, &rcH);

pHour=new CHourglass;

[If a prior SetExtent failed,
run the server and execute the verb now.]

m_pIOleObject->DoVerb(iVerb, NULL, m_pImpIOleClientSite, 0
, m_hWnd, &rcH);

delete pHour;

//If object changes, IAdviseSink::OnViewChange will see it.
return FALSE;
}

Here we show an hourglass in case DoVerb takes a while, and then we pass the verb value to DoVerb along with our client site, a window handle, and a rectangle.9 These latter two arguments provide a way for certain objects to play in place completely within the confines of the call to DoVerb. This is not in-place activation, but simply a way for something such as a video clip to play itself in the container's window—clipped to the rectangle—before returning from DoVerb. After the object returns, it is not allowed to leave anything in the container and is not allowed to hold on to the window handle. For a container, we want to pass these arguments to all objects whether or not they use them.

Somehow the container has to know when to activate an object. In some cases, the container can deliberately show or hide an object's user interface with OLEIVERB_SHOW and OLEIVERB_HIDE, or it can specifically request that the object open itself in a window for editing through OLEIVERB_OPEN. (There are also verbs for in-place activation, as we'll see in Chapter 22.) An object's custom verbs, however, are sent to DoVerb only in response to user action, which includes the creation of a new object, as we saw earlier.

There are two other suitable actions besides creation. The first occurs when the user double-clicks on an object in the container, which tells the container to execute the object's primary, or default, verb. This is accomplished by calling IOleObject::DoVerb(OLEIVERB_PRIMARY), in which OLEIVERB_PRIMARY has the value 0. The meaning of this verb changes from object to object, but it is generally what the user expects. Sound and video objects will play, text or graphical objects will edit, and so on. Whatever the behavior is, Patron handles this case in CPage::OnLeftDoubleClick (in PAGEMOUS.CPP):


BOOL CPage::OnLeftDoubleClick(UINT uKeys, UINT x, UINT y)
{
if (HTNOWHERE!=m_uHTCode)
return m_pTenantCur->Activate(OLEIVERB_PRIMARY);

return FALSE;
}

The second user action that invokes a verb is its selection from a menu that the container populates with the verbs found for the object in the registry. The pop-up menu must appear in two places: on the container's Edit menu and on an object context menu, displayed when the user clicks the right mouse button over the site. In either case, the menu displays the available verbs along with an item called Convert, which is used to invoke the Convert dialog box, as discussed later in this chapter. An example of this menu for a Sound object with two verbs is shown in Figure 17-8 on the following page.

Figure 17-8.

The object verb menu shown in response to a right mouse button click.

Fortunately a container doesn't have to do much work to create this pop-up menu because of the handy OLE UI Library function named OleUIAddVerbMenu:


STDAPI_(BOOL) OleUIAddVerbMenu(LPOLEOBJECT lpOleObj
, LPCTSTR lpszShortType, HMENU hMenu, UINT uPos
, UINT uIDVerbMin, UINT uIDVerbMax, BOOL bAddConvert
, UINT idConvert, HMENU FAR *lphMenu);

You pass to this function the object's IOleObject pointer, a string describing its type (if NULL, OleUIAddVerbMenu will use the AuxUserType form 2 entry in the registry), the menu on which the pop-up menu is to appear, the position on the menu to create the pop-up, the minimum and maximum WM_COMMAND identifiers to assign to the verbs, a flag indicating whether to add the Convert item, the ID to assign to the Convert item, and a pointer to a variable that receives the pop-up menu handle on return. Patron calls this function within CTenant::AddVerbMenu whenever it needs to build a menu in this manner:


void CTenant::AddVerbMenu(HMENU hMenu, UINT iPos)
{
HMENU hMenuTemp;
LPOLEOBJECT pObj=m_pIOleObject;

//If we're static, say we have no object.
if (TENANTTYPE_STATIC==m_tType)
pObj=NULL;

OleUIAddVerbMenu(pObj, NULL, hMenu, iPos, IDM_VERBMIN
, IDM_VERBMAX, TRUE, IDM_EDITCONVERT, &hMenuTemp);

return;
}

When the user selects a verb from this menu, the container's main window sees a WM_COMMAND message (CPatron::OnCommand in PATRON.CPP) with a command ID equal to IDM_VERBMIN+<verb index>, so we would call DoVerb using commandID-IDM_VERBMIN. When the user selects the Convert item, we'll see a WM_COMMAND with IDM_EDITCONVERT.

Patron creates this pop-up menu whenever it sees WM_INITPOPUP in the main window's message procedure. We eventually end up in CPage::FQueryObjectSelected (in PAGE.CPP), which calls CTenant::AddVerbMenu if there is a tenant selected. If there are no tenants at all, we still call OleUIAddVerbMenu but pass a lot of NULLs, which causes the function to create a single disabled menu item named Object.

Patron also creates this menu item inside CPage::OnRightDown (in PAGEMOUS.CPP). When a right click happens, Patron selects the tenant under the mouse, builds a context menu, adds to it the object's verbs with CTenant::AddVerbMenu, and then displays that menu with the Windows API TrackPopupMenu. Any command selected from this menu will also appear in the container's main window procedure, as do all other menu commands. The commands themselves are routed through CPatronDoc::ActivateObject, CPages::ActivateObject, CPage::ActivateObject, and finally CTenant::Activate. (I didn't say Patron had the most elegant design!)

Note: If your application has a status line, you might want to have it display status information for these verbs as the user selects them from the menu. Patron does this by watching for WM_MENUSELECT messages in CPatronFrame::FMessageHook with the appropriate identifiers. The recommended strings are documented in the Windows user interface guidelines.

8 In the sample code, you'll see a call to OleRun and IOleObject::SetExtent if an m_fSetExtent flag is TRUE. This flag will be set if resizing a tenant earlier did not pass the call all the way through to the running object. This happens only for objects marked OLEMISC_RECOMPOSEONRESIZE. The call to SetExtent here simply ensures that the extents are set right before we activate the object.

9 The NULL is a pointer to a MSG structure used with in-place activation.