Implement Site Interfaces and Add Site Variables

We first add to whatever we decide is a suitable site the necessary variables to manage an embedded object and implement the IOleClientSite and IAdviseSink interfaces. In Patron, I already have the CTenant class, which I've converted here to an object with a reference count and so on, but because this object is only a site, it needs no CLSID, no class factory support, and no registry entries: it's only a simple object with some interfaces. One effect of this change is that I had a few places around my code that called the C++ delete operator directly on a tenant. Now that the tenant object has a reference count and an IUnknown implementation, these delete calls are replaced with Release.

I added a few variables to the CTenant class to maintain the reference count (m_cRef), the interface implementations (m_pImpIOleClientSite and m_pImpIAdviseSink), and various interfaces we hold on the embedded object (m_pIOleObject and m_pIViewObject2). The other new variable is m_tType of type TENANTTYPE, an enumeration that identifies the type of embedded content object in this site:


typedef enum
{
TENANTTYPE_NULL=0,
TENANTTYPE_STATIC,
TENANTTYPE_EMBEDDEDOBJECT,
TENANTTYPE_EMBEDDEDFILE,
TENANTTYPE_EMBEDDEDOBJECTFROMDATA
} TENANTTYPE, *PTENANTTYPE;

In later chapters, we'll add more flags to this set to identify linked objects as well. In any case, Patron initializes these variables to 0 or NULL in CTenant::CTenant. In CTenant::Open, Patron creates the site's interfaces, deleting them in its destructor. The object's pointers themselves are initialized when we create an object (as we'll see later) and released in CTenant::Close.

I also added a number of new member functions to CTenant: StorageGet, ShowAsOpen, ShowYourself, AddVerbMenu, TypeGet, CopyEmbeddedObject, NotifyOfRename, ObjectClassFormatAndIcon, SwitchOrUpdateDisplayAspect, and EnableRepaint. Several of them are used from within the implementations of the site interfaces that are found in CImpIOleClientSite (ICLISITE.CPP) and CImpIAdviseSink (IADVSINK.CPP).

Patron makes use of only two members of IAdviseSink: OnViewChange and OnClose. When a site receives OnViewChange for the aspect that you display (content, icon, and so on), you only need to repaint the site (thus, redraw the object) and set your document's dirty flag, in whatever way you do that:


STDMETHODIMP_(void) CImpIAdviseSink::OnViewChange(DWORD dwAspect
, LONG lindex)
{
//Repaint only if this is the right aspect.
if (dwAspect==m_pTen->m_fe.dwAspect)
{
m_pTen->m_pPG->m_fDirty=TRUE;
m_pTen->Repaint();
}

return;
}

As pointed out in Chapter 11, OnViewChange tells the site that the object's presentation as opposed to its data has changed. Because a container shows an object's presentation in the site, we want to watch view changes, not data changes, in order to keep the image in the site current. In Patron, CTenant::Repaint invalidates the site's area in the container's client area and forces a redraw. This calls the embedded object's IViewObject2::Draw, as we'll see later. Then, when an object is running and sending us these notifications, we'll immediately redraw the object's image as changes happen to it. In that way, the site image keeps up-to-date with the object's own user interface.5

In IAdviseSink::OnClose, I've included a single call to CTenant::ShowYourself(FALSE) to deal with some potentially misbehaving OLE 1 servers as described on OLE1.WRI on the companion CD:


STDMETHODIMP_(void) CImpIAdviseSink::OnClose(void)
{
m_pTen->ShowAsOpen(FALSE);
return;
}

The other IAdviseSink members, OnDataChange, OnSave, and OnRename, are not important to a container, but they are important to an object handler and the data cache being used in the container's process. Such notifications are used to update the handler's internal state, but we don't need to take an interest in them ourselves.

The behavior of IAdviseSink::OnClose is similar to that of IOleClientSite::OnShowWindow, which is one of the members we need to implement in that interface. In Patron, we also implement SaveObject and ShowObject, leaving GetMoniker, GetContainer, and RequestNewObjectLayout unimplemented for now (returning E_NOTIMPL). We will return to these functions in later chapters as we make further enhancements to Patron.

As described earlier in this chapter, OnShowWindow tells the container to draw a hatch pattern across the site after repainting the object. The function CTenant::ShowAsOpen toggles this state and repaints the object with a hatch pattern across it, as described in the next section. So IOleClientSite::OnShowWindow simply delegates to this function in the tenant, as does IAdviseSink::OnClose, discussed earlier:


STDMETHODIMP CImpIOleClientSite::OnShowWindow(BOOL fShow)
{
m_pTen->ShowAsOpen(fShow);
return NOERROR;
}

The implementation of IOleClientSite::SaveObject also delegates to a function in CTenant, that function being Update:


STDMETHODIMP CImpIOleClientSite::SaveObject(void)
{
m_pTen->Update();
return NOERROR;
}

CTenant::Update ensures that the object in this site is fully saved by querying for IPersistStorage and calling OleSave, then IPersistStorage::SaveCompleted, and then IPersistStorage::Release. Patron has been doing this since Chapter 12, and the code requires no modification:


BOOL CTenant::Update(void)
{
LPPERSISTSTORAGE pIPS;

if (NULL!=m_pIStorage)
{
m_pObj->QueryInterface(IID_IPersistStorage, (PPVOID)&pIPS);

//This fails for static objects, so improvise if that happens.
if (FAILED(OleSave(pIPS, m_pIStorage, TRUE)))
{
//This is essentially what OleSave does.
WriteClassStg(m_pIStorage, m_clsID);
pIPS->Save(m_pIStorage, TRUE);
}

pIPS->SaveCompleted(NULL);
pIPS->Release();

m_pIStorage->Commit(STGC_DEFAULT);
}

return FALSE;
}

OleSave can fail, but because all it does is call WriteClassStg and IPersistStorage::Save, we can duplicate its behavior here as needed.

Finally, ShowObject tells the container to bring the object (that is, the site) into view if at all possible, scrolling only if necessary. An object calls this function before it calls OnShowWindow when it's activated in a separate window. Bringing the site into view ensures that the user can see the changes reflected in the site as they happen in the object's window. Of course, because ShowObject concerns only the user interface, it's not truly necessary for the actual embedding operation. If it's too much trouble or not applicable to your container, feel free to ignore it altogether. Patron doesn't see scrolling as a problem, so it implements this feature through CTenant::ShowYourself, to which ShowObject delegates:


STDMETHODIMP CImpIOleClientSite::ShowObject(void)
{
m_pTen->ShowYourself();
return NOERROR;
}

void CTenant::ShowYourself(void)
{
RECTL rcl;
RECT rc;
POINT pt1, pt2;

//Scrolling deals in device units; get our rectangle in those.
RectGet(&rcl, TRUE);

//Get window rectangle offset for current scroll position.
GetClientRect(m_hWnd, &rc);
OffsetRect(&rc, m_pPG->m_xPos, m_pPG->m_yPos);

//Check whether object is already visible. (Macro in bookguid.h.)
SETPOINT(pt1, (int)rcl.left, (int)rcl.top);
SETPOINT(pt2, (int)rcl.right, (int)rcl.bottom);

if (PtInRect(&rc, pt1) && PtInRect(&rc, pt2))
return;

//Check whether upper left is within upper left quadrant.
if (((int)rcl.left > rc.left
&& (int)rcl.left < ((rc.right+rc.left)/2))
&& ((int)rcl.top > rc.top
&& (int)rcl.top < ((rc.bottom+rc.top)/2)))
return;

//These are macros in INC\BOOK1632.H.
SendScrollPosition(m_hWnd, WM_HSCROLL, rcl.left-8);
SendScrollPosition(m_hWnd, WM_VSCROLL, rcl.top-8);
return;
}

A good rule of thumb here is to avoid scrolling if at all possible, so ShowYourself first checks to see whether the site's rectangle (which is the same as the object's rectangle in the container) is already visible—that is, whether both upper left and lower right corners are already visible in the page window. If so, nothing needs to happen and we can exit the routine. If this first check fails, either the site is not visible at all or the site is too big to be entirely shown in the window. If the upper left corner of the site is in the upper left quadrant of the window, it must be true that the site is not completely visible but that enough of it is visible that we would still want to avoid scrolling. If this second check fails, ShowYourself capitulates and scrolls the window so that the upper left corner of the site is visible just below the upper left corner of the window. The SendScrollPosition macros (defined in INC\BOOK1632.H) send WM_*SCROLL messages with SB_THUMBPOSITION messages, which are processed in PagesWndProc of PAGEWIN.CPP.

At this point, you'll have completed the site interfaces for a container—which is the simple part—with the exception of shading the site when necessary. That is our next task.

5 An OLE 1 server does not send notifications for every modification, so in this case, you won't see updates as frequently.