Send Notifications

In the context of OLE Documents, an object can send a number of notifications and requests to both the container's IAdviseSink and IOleClientSite interfaces. The only true asynchronous notifications are calls to IAdviseSink, whereas others are synchronous calls to IOleClientSite::SaveObject. Nevertheless, lumping these together as notifications in an embedded object implementation is quite convenient. It enables us to make one function for our object that will send the proper notification to the appropriate interface whenever asked. That function is CFigure::SendAdvise, which accepts any of these codes defined in COSMOLE.H:


//Codes for CFigure::SendAdvise
//......Code.....................Method called in CFigureSendAdvise...
#define OBJECTCODE_SAVED 0 //IOleAdviseHolder::SendOnSave
#define OBJECTCODE_CLOSED 1 //IOleAdviseHolder::SendOnClose
#define OBJECTCODE_RENAMED 2 //IOleAdviseHolder::SendOnRename
#define OBJECTCODE_SAVEOBJECT 3 //IOleClientSite::SaveObject
#define OBJECTCODE_DATACHANGED 4 //IDataAdviseHolder::SendOnDataChange
#define OBJECTCODE_SHOWWINDOW 5 //IOleClientSite::OnShowWindow(TRUE)
#define OBJECTCODE_HIDEWINDOW 6 //IOleClientSite::OnShowWindow(FALSE)
#define OBJECTCODE_SHOWOBJECT 7 //IOleClientSite::ShowObject

Each code is implemented using an appropriate site interface or advise holder:


//FIGURE.CPP
void CFigure::SendAdvise(UINT uCode)
{
switch (uCode)
{
case OBJECTCODE_SAVED:
if (NULL!=m_pIOleAdviseHolder)
m_pIOleAdviseHolder->SendOnSave();

break;

case OBJECTCODE_CLOSED:
if (NULL!=m_pIOleAdviseHolder)
m_pIOleAdviseHolder->SendOnClose();

break;

case OBJECTCODE_RENAMED:
//Call IOleAdviseHolder::SendOnRename (later)
break;

case OBJECTCODE_SAVEOBJECT:
if (FlsDirty() && NULL!=m_pIOleClientSite)
m_pIOleClientSite->SaveObject();

break;

case OBJECTCODE_DATACHANGED:
//No flags are necessary here.
if (NULL!=m_pIDataAdviseHolder)
{
m_pIDataAdviseHolder->SendOnDataChange(m_pIDataObject
, 0, 0);
}

break;

case OBJECTCODE_SHOWWINDOW:
if (NULL!=m_pIOleClientSite)
m_pIOleClientSite->OnShowWindow(TRUE);

break;

case OBJECTCODE_HIDEWINDOW:
if (NULL!=m_pIOleClientSite)
m_pIOleClientSite->OnShowWindow(FALSE);

break;

case OBJECTCODE_SHOWOBJECT:
if (NULL!=m_pIOleClientSite)
m_pIOleClientSite->ShowObject();

break;
}

return;
}

This function eliminates the need to check for NULL pointers anywhere else and distills all notifications down to one function and one argument. Then we don't have to remember which interface—IAdviseSink, IOleClientSite, IOleAdviseHolder, or IDataAdviseHolder—we used to send which notification to the container.

We've seen a number of places in this chapter to which we send various notifications. These are listed in Table 18-1. The only events we haven't covered so far are those for closing a document and for data changes.

Event

Notifications (in the Order Shown)

Closing a document
(CCosmoDoc::~CcosmoDoc)

IOleClientSite::SaveObject
IOleClientSite::OnShowWindow(FALSE)
IOleAdviseHolder::SendOnClose
(all of this before CoDisconnectObject)

Data changes (CCosmoDoc::FDirtySet)

IDataAdviseHolder::SendOnDataChange

IOleObject::Close (if saving)

IOleClientSite::SaveObject
IOleAdviseHolder::SendOnSave

IOleObject::DoVerb(HIDE)

IOleClientSite::OnShowWindow(FALSE)

IOleObject::DoVerb(SHOW)
(includes any verb that shows)

IOleClientSite::ShowObject
IOleClientSite::OnShowWindow(TRUE)


Table 18-1.

When to send notifications from an embedded object.

You'll notice that we don't use or implement OBJECTCODE_RENAMED anywhere because it's used only for linking and requires a moniker, which we won't add until Chapter 21. In addition, you'll notice that the server never calls IAdviseSink::OnViewChange because a local server never implements IViewObject2. Instead, the data cache watches IAdviseSink::OnDataChange and generates OnViewChange notifications itself.

I should point out that you might need to optimize the code when you actually send OnDataChange to advise sinks. The preceding table describes this situation as any time data changes in your application. To reflect that in a container, OLE must call your IDataObject::GetData to request a new presentation. If your presentation is complex—perhaps a metafile with 5000 records—this operation will not be fast. In such cases, you might want to defer sending the notification for a specific time after the most recent change—maybe 1 or 2 seconds. This would allow end users to make rapid changes without having to continually wait for you to generate a new presentation. Only when they stop making changes will you actually send an OnDataChange. You can also consider making updates part of your idle time processing. If you do defer the OnDataChange notification in any way, send one immediately upon shutdown to ensure that OLE can get a final presentation from you.

And now, as the stork would say, "Congratulations! You're a mother!" Well, at least a mother of an embedded object server that is fully functional with a container application. At this point, you should be able to run Insert Object from a container to launch your server, make changes in the object, and see those changes reflected in a shaded object site. If changes are not being reflected in the container, either it's displaying a different aspect than the one you are changing, or, when your IDataObject::GetData is asked for CF_METAFILEPICT or CF_BITMAP, you're returning the wrong STGMEDIUM. In developing Cosmo, I tore my hair out trying to understand why the container was not reflecting changes. I discovered that the tymed I was storing in GetData's STGMEDIUM was TYMED_HGLOBAL instead of TYMED_MFPICT for the CF_METAFILEPICT format. Subtle, but ever so important.

If the container site is not shading itself, you might not be calling IOleClientSite::OnShowWindow at the appropriate times, or the container itself might be at fault. To determine whether you are doing it correctly, try your server with a dependable container, such as Chapter 17's version of Patron.

When you close your server, you should see no prompts asking to save unless you get OLECLOSE_PROMPTSAVE in IOleObject::Close. In this case and in the case in which you delete the running object from a container, your server should be completely purged from memory. If not, your shutdown conditions are not being met, and you are not closing your main window and exiting WinMain. If you are shutting down completely, you should see your object's presentation in the container's document. Double-clicking on that object should again launch your server, but this time you are asked to reload that saved object and edit that data instead.