Implement Partial In-Place Container Interfaces

To build any other in-place–activation code, we'll need at least stub implementations of IOleInPlaceSite, IOleInPlaceFrame, and, if applicable, IOleInPlaceUIWindow. Each interface is part of the appropriate container-side object. Patron implements the first as a contained class in CTenant (through CImpIOleInPlaceSite in IIPSITE.CPP), the second as part of CPatronFrame (in PATRON.CPP), and the third as a contained class in CPatronDoc (CImpIOleInPlaceUIWindow in IIPUIWIN.CPP). The GetWindow member of the site interface returns the pages window that occupies a document's client area—in other containers this might be simply the document window itself. The same member function in the other two interfaces returns the frame and document window handles as appropriate.

We don't need to implement every part of these interfaces right away. We'll get to everything in turn. At this point in our implementation, we need to do the following:

Implement the trivial GetWindow function of each interface.

Implement SetActiveObject in the frame and document interfaces to save the IOleInPlaceActiveObject pointer passed to each.

Implement the IOleInPlaceSite members of CanInPlaceActivate, OnInPlaceActivate, OnInPlaceDeactivate, and GetWindowContext.

Return E_NOTIMPL from everything else for the time being.

The following sections will look at steps 2 and 3 in more detail. You'll also need to ensure that each object, site, document, and frame supports the correct interfaces in QueryInterface. Each object will need to respond to IOleWindow as well as to the specific interface in question. In addition, the frame's implementation will need to respond to IOleInPlaceUIWindow because that is the base interface of IOleInPlaceFrame.


EXPERIENCE: The Site Requires Access to the Frame and the Document

A site object will generally need access both to frame and document variables and to member functions because the interaction between the in-place object and the site will involve user interface elements for the frame and the document. This is especially true because IOleInPlaceSite::GetWindowContext must return interface pointers for the frame and document windows. For this reason, Patron stores its CPatronFrame pointer in a global variable, g_pFR. Not the best design, but it works.


Implement SetActiveObject for the Frame and the Document

The SetActiveObject member in IOleInPlaceUIWindow (and thus IOleInPlaceFrame) is the function that the container uses to obtain the UI-active object's IOleInPlaceActiveObject interface, which the container must hold for later calls that we'll make to it. In Patron, both CPatronFrame and CPatronDoc have a member variable m_pIOleIPActiveObject for this purpose.

SetActiveObject will be called with both NULL and non-NULL pointers.5 When NULL is passed, release whatever interface pointer you have. When non-NULL is passed, release the old pointer, save a copy of the new one, and call AddRef through that new pointer, as shown in the code at the top of the following page.


if (NULL!=m_pIOleIPActiveObject)
m_pIOleIPActiveObject->Release();

//NULLs m_pIOleIPActiveObject if pIIPActiveObj is NULL
m_pIOleIPActiveObject=pIIPActiveObj;

if (NULL!=m_pIOleIPActiveObject)
m_pIOleIPActiveObject->AddRef();

Implement Crucial IOleInPlaceSite Members

IOleInPlaceSite is the critical path of communication between an in-place object and the container, so for now we'll need to do most of our work in this interface. In Patron, the three members CanInPlaceActivate, OnInPlaceActivate, and OnInPlaceDeactivate are typical and rather short:


STDMETHODIMP CImpIOleInPlaceSite::CanInPlaceActivate(void)
{
if (DVASPECT_CONTENT!=m_pTen->m_fe.dwAspect)
return ResultFromScode(S_FALSE);

if (TENANTTYPE_EMBEDDEDOBJECT!=m_pTen->m_tType)
return ResultFromScode(S_FALSE);

return NOERROR;
}

STDMETHODIMP CImpIOleInPlaceSite::OnInPlaceActivate(void)
{
//m_pIOleIPObject is our in-place flag.
m_pTen->m_pObj->QueryInterface(IID_IOleInPlaceObject
, (PPVOID)&m_pTen->m_pIOleIPObject);
return NOERROR;
}

STDMETHODIMP CImpIOleInPlaceSite::OnInPlaceDeactivate(void)
{
m_pTen->Activate(OLEIVERB_DISCARDUNDOSTATE, NULL);
ReleaseInterface(m_pTen->m_pIOleIPObject);
return NOERROR;
}

For most containers, CanInPlaceActivate will not be too complex. Patron disallows in-place activation (returning S_FALSE) when the object is not embedded or is being displayed using an aspect other than DVASPECT_CONTENT. Other containers can use other conditions, of course.

OnInPlaceActivate and OnInPlaceDeactivate tell the container to put itself in or take itself out of an in-place state. Patron's state consists entirely of the object's IOleInPlaceObject pointer (saved in CTenant::m_pIOleIPObject), which is managed in these two functions. Patron queries for and saves the pointer when the object is activated, releasing it when the object is deactivated. The pointer, which we need for making other function calls later, doubles as a flag to tell us whether we are handling an in-place session at the time. We'll come back to the IOleObject::DoVerb call later when we discuss Undo operations.

IOleInPlaceSite::GetWindowContext is the hairy beast of the group, requiring more code because it has more arguments to mess with. This function stores pointers to the container's IOleInPlaceFrame and IOleInPlaceUIWindow interfaces in two out-parameters (ppIIPFrame and ppIIPUIWindow), specifies the initial position and clipping rectangles for the object (in prcPos and prcClip), and fills an OLEINPLACEFRAMEINFO structure with accelerator information:


STDMETHODIMP CImpIOleInPlaceSite::GetWindowContext
(LPOLEINPLACEFRAME *ppIIPFrame, LPOLEINPLACEUIWINDOW
*ppIIPUIWindow, LPRECT prcPos, LPRECT prcClip
, LPOLEINPLACEFRAMEINFO pFI)
{
PCPatronDoc pDoc;
RECTL rcl;

*ppIIPUIWindow=NULL;

*ppIIPFrame=(LPOLEINPLACEFRAME)g_pFR;
g_pFR->AddRef();

pDoc=(PCPatronDoc)SendMessage(GetParent(m_pTen->m_hWnd)
, DOCM_PDOCUMENT, 0, 0L);

if (NULL!=pDoc)
{
pDoc->QueryInterface(IID_IOleInPlaceUIWindow
, (PPVOID)ppIIPUIWindow);
}

//Now get rectangles and frame information.
m_pTen->RectGet(&rcl, TRUE);
RECTFROMRECTL(*prcPos, rcl);

//Include scroll position here.
OffsetRect(prcPos, -(int)m_pTen->m_pPG->m_xPos
, -(int)m_pTen->m_pPG->m_yPos);

SetRect(prcClip, 0, 0, 32767, 32767);

pFI->cb=sizeof(OLEINPLACEFRAMEINFO);
#ifdef MDI
pFI->fMDIApp=TRUE;
#else
pFI->fMDIApp=FALSE;
#endif

pFI->hwndFrame=g_pFR->Window();

pFI->haccel=g_pFR->m_hAccelIP;
pFI->cAccelEntries=CINPLACEACCELERATORS;

return NOERROR;
}

Patron stores the global CPatronFrame pointer g_pFR as the IOleInPlaceFrame pointer (calling AddRef, of course) because CPatronFrame inherits from the interface directly. We have to ask the document for its IOleInPlaceUIWindow, however.

The container is responsible for returning two rectangles here. The position rectangle tells the object exactly which rectangle it occupies in the container in device units (pixels). In Patron's case, this is the rectangle for the tenant implementing this site, offset for the current scroll position. The object will use this position rectangle as the area in which it displays its data, surrounding that area with any adornments it wants. If this area is not the same as the object's own extents, the ratio of the two determines the scaling factor. An object can choose to resize itself to fit in this rectangle, or it can choose to not change its scale and provide scrollbars or other devices as necessary.

The second rectangle, the clipping rectangle, specifies where the object can legally display anything—including adornments outside the object's data area. The object is downright criminal if it dares to place anything outside the clipping rectangle. This is the law to prevent the object from overlapping parts of the container's own UI—such as sibling toolbars—that are visibly outside the document area itself. Patron actually makes no restrictions because the object's window will be clipped to the pages window that occupies the usable client area of the document (and which is moved to accommodate any document tools the object creates). So we store the maximum rectangle (0–32,767 in both extents) in prcClip, again in device units. Keep in mind that the position rectangle can be larger than or extend outside the clipping rectangle. The object will scale to the position rectangle, but it will be displayed only in the intersection of the position and clipping rectangles.

The OLEINPLACEFRAMEINFO structure that you fill here provides the object, if it comes from a local server, with the information it requires for calling OleTranslateAccelerator. In-process objects do not use this information. OleTranslateAccelerator needs to know whether the container uses MDI (so that it can call TranslateMDISysAccel appropriately), as well as the window to receive accelerator commands. The accelerators you include here should be only those you want active during an in-place session. (Patron's accelerators are listed for the identifier IDR_INPLACEACCELERATORS in PATRON.RC.)

The cb field in OLEINPLACEFRAMEINFO indicates the version of the structure itself. (Later revisions of OLE might change the structure.) The object will fill this field before calling GetWindowContext. This means that the container must not modify the field but rather use it to store the right information in the right part of the structure.

With only this level of implementation, a container can activate an object to the point shown in Figure 22-7 on page 1024. Trouble is, we have no code in the container to deactivate the object! So we'd better add support for the rest of an object's activation needs.

5 Again, ignore the pszObjName argument, which is no longer used.