Save and Load the Document with Embedded Objects

At some point, it would be nice to save all the objects we've been creating and editing so that we can reload them at a later time. We've by and large covered all the steps for saving an object in a document: providing an IStorage, calling OleSave when asked through IOleClientSite::SaveObject or when closing the object, and doing the same through CTenant::Update.

On a larger scale, Patron's document saving starts in CPatronDoc::Save, which first asks CPages to update, which in turn asks the currently open CPage to update, which in turn asks each tenant to update by using the preceding code. After all that, the document commits itself, and because it is the owner of the root storage, that commitment writes the file to disk. Little of this storage code has changed from previous versions of Patron.

Patron, by the way, is designed—and I won't argue that this is the best design—to keep only the current page open. This means that when you switch pages, all open or running objects on the old page are closed to the passive state and all objects on the new page are opened to the loaded state.

Loading a document in Patron starts with opening a root storage for the document and initializing the pages. Then Patron opens the current page by using CPage::Open. This, in turn, re-creates all the tenants on the page, but instead of calling CTenant::Create, Patron calls CTenant::Load, indicating the object's storage, what is in this storage, and the rectangle occupied by the tenant on the page:


BOOL CTenant::Load(LPSTORAGE pIStorage, PTENANTINFO pti)
{
HRESULT hr;
LPUNKNOWN pObj;
DWORD dwState=TENANTSTATE_DEFAULT;

if (NULL==pIStorage śś NULL==pti)
return FALSE;

/*
* If we already initialized once, clean up, releasing
* everything before we attempt to reload. This happens
* when using the Convert dialog.
*/
if (m_fInitialized)
{
//Preserve all states except open.
dwState=(m_dwState & ~TENANTSTATE_OPEN);
m_cRef++; //Prevent accidental closure.

//This should release all holds on our IStorage as well.
if (NULL!=m_pIViewObject2)
{
m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, NULL);
ReleaseInterface(m_pIViewObject2);
}

ReleaseInterface(m_pIOleObject);
ReleaseInterface(m_pObj);

m_pIStorage=NULL; //We'll have already released this.
m_cRef--; //Match safety increment above.
}

m_fInitialized=TRUE;

//Open storage for this tenant.
if (!Open(pIStorage))
return FALSE;

hr=OleLoad(m_pIStorage, IID_IUnknown, NULL, (PPVOID)&pObj);

if (FAILED(hr))
{
Destroy(pIStorage);
return FALSE;
}

m_fSetExtent=pti->fSetExtent;
ObjectInitialize(pObj, &pti->fe, NULL);

//Restore original state before reloading.
m_dwState=dwState;

RectSet(&pti->rcl, FALSE, FALSE);
return TRUE;
}

Most of this is unchanged from previous versions. OleLoad brings passive objects into the loaded state and returns an interface pointer. Now, however, to support embedded objects, we call ObjectInitialize to perform the same initialization sequence as was required after OleCreate and to position the object on the page before repainting. In addition, we check to ensure that we are not simply reloading this object from an already initialized state, which might occur when we deal with conversion and emulation. We'll see why this is important shortly.

Patron's file handling still has one small modification that you can find in CPatronDoc::Rename. This is called whenever the user does a File Save As or otherwise changes the name of a document. After doing the usual document renaming work, Patron calls CPage::NotifyTenantsOfRename, which cycles through all the tenants in the current page, calling CTenant::NotifyOfRename with the new filename. The tenant now uses this information to call IOleObject::SetHostNames once again, updating the information it passed through object initialization:


void CTenant::NotifyOfRename(LPTSTR pszFile, LPVOID pvReserved)
{
TCHAR szObj[40];
TCHAR szApp[40];

if (NULL==m_pIOleObject)
return;

if (TEXT('\0')==*pszFile)
{
LoadString(m_pPG->m_hInst, IDS_UNTITLED, szObj
, sizeof(szObj));
}
else
GetFileTitle(pszFile, szObj, sizeof(szObj));

LoadString(m_pPG->m_hInst, IDS_CAPTION, szApp, sizeof(szApp));
m_pIOleObject->SetHostNames(szApp, szObj);
return;
}

If you leave out this small part, your current document name will not be reflected in open server windows, which is not very user friendly.