Modify the Server's User Interface

I admit that I seriously loathe writing user interface code because it's the one place that you cannot be the least bit wrong without someone noticing. In addition, user interface specifications seldom identify who is responsible for doing what, and sometimes the specs don't articulate all possible cases. I guess that's why they're called guidelines. The situation in which you execute IOleObject::DoVerb and show a server window is one such case that is not exactly well defined. User interface guidelines for OLE Documents typically devote all their discussion to in-place activation. Such is life for us in the trenches, shooting in the dark at an unknown and unseen target.

So what I'm describing here is not official. It is pieced together from what I've seen other applications do. (That's how we get standards in the first place, isn't it?)

All the following changes to the user interface should take place when IOleObject::SetHostNames is called. That function tells you that your application is an embedded object, as well as the names of the container application and the container document for which you need to make these changes. SetHostNames is always called before DoVerb, so these changes should be in effect before you show an editing window:

Remove the New, Open, Close, and Save commands from your File menu. (Remove any toolbar buttons that invoke the same commands as well.)

Change the name of the Save As command on the File menu to Save Copy As. You can usually keep the same command identifier for this modified item. You might also want to remove any toolbar button for this function, but that's up to you. Save Copy As essentially creates an export function that does not remember the filename after the copy is written. In addition, if you have an import function, as Cosmo does, you can leave that on the menu and the toolbar. If you have a status line, you might also want to change the message displayed for this item (which Cosmo does not do).

Change the Exit command on the File menu to Exit And Return To <container document>; the string <container document> is pointed to by the pszObj argument of SetHostNames. Again, you might want to change any toolbar and status line UI to accommodate this. (I know it seems silly that the argument containing the document name is called pszObj, but, hey, it's only software—call it anything you like.)

Change your caption bar to read "<object type> in <container document>"; <object type> is the user-readable name of your object, such as "Cosmo Figure", and <container document> is the pszObj argument from SetHostNames. If your application is an SDI application or if it is MDI but the document is maximized, this string appears in the main application window's title bar prefixed with "<application name> -". If your application is an MDI application without a maximized document window, the frame caption remains the same, and this string appears in the document's title bar. (MDI automatically handles the maximized document case by concatenating the frame window's caption with the - character and the document's caption.)

Cosmo makes these changes from within IOleObject::SetHostNames by calling CCosmoFrame::UpdateEmbeddingUI because the frame controls the menus and the toolbar. UpdateEmbeddingUI is actually capable of switching between an embedding state and a nonembedding state in case I ever decide to allow Cosmo to service multiple objects as well as other nonobject documents, as described in "(Optional) MDI Servers, User Interface, and Shutdown" near the end of this chapter.


void CCosmoFrame::UpdateEmbeddingUI(BOOL fEmbedding
, PCDocument pDoc, LPCTSTR pszApp, LPCTSTR pszObj)
{
HMENU hMenu;
TCHAR szTemp[256];

//First let's play with File menu.
hMenu=m_phMenu[0];

//Remove or add File New, Open, and Save items.
if (fEmbedding)
{
DeleteMenu(m_phMenu[0], IDM_FILENEW, MF_BYCOMMAND);
DeleteMenu(m_phMenu[0], IDM_FILEOPEN, MF_BYCOMMAND);
DeleteMenu(m_phMenu[0], IDM_FILECLOSE, MF_BYCOMMAND);
DeleteMenu(m_phMenu[0], IDM_FILESAVE, MF_BYCOMMAND);

//Save As->Save Copy As
ModifyMenu(m_phMenu[0], IDM_FILESAVEAS, MF_BYCOMMAND
, IDM_FILESAVEAS, PSZ(IDS_SAVECOPYAS));
}
else
{
InsertMenu(m_phMenu[0], 0, MF_BYPOSITION, IDM_FILENEW
, PSZ(IDS_NEW));
InsertMenu(m_phMenu[0], 1, MF_BYPOSITION, IDM_FILEOPEN
, PSZ(IDS_OPEN));
InsertMenu(m_phMenu[0], 2, MF_BYPOSITION, IDM_FILESAVE
, PSZ(IDS_SAVE));
InsertMenu(m_phMenu[0], 3, MF_BYPOSITION, IDM_FILECLOSE
, PSZ(IDS_SAVE));

//Save Copy As->Save As
ModifyMenu(m_phMenu[0], IDM_FILESAVEAS, MF_BYCOMMAND
, IDM_FILESAVEAS, PSZ(IDS_SAVEAS));
}

//Change Exit to Exit & Return to xx or vice versa for SDI.
if (fEmbedding)
wsprintf(szTemp, PSZ(IDS_EXITANDRETURN), (LPSTR)pszObj);
else
lstrcpy(szTemp, PSZ(IDS_EXIT));

ModifyMenu(m_phMenu[0], IDM_FILEEXIT, MF_STRING, IDM_FILEEXIT
, szTemp);
DrawMenuBar(m_hWnd);

//Now let's play with toolbar.
m_pTB->Show(IDM_FILENEW, !fEmbedding);
m_pTB->Show(IDM_FILEOPEN, !fEmbedding);
m_pTB->Show(IDM_FILECLOSE, !fEmbedding);
m_pTB->Show(IDM_FILESAVE, !fEmbedding);

//Enable what's left appropriately.
UpdateToolbar();

//Now let's play with title bar.

//IDS_EMBEDDINGCAPTION is MDI/SDI sensitive in COSMO.RC.
wsprintf(szTemp, PSZ(IDS_EMBEDDINGCAPTION), pszObj);

/*
* Remember that in MDI situations, Windows takes care of
* frame window caption bar when document is maximized.
*/
#ifdef MDI
SetWindowText(pDoc->Window(), szTemp);
#else
SetWindowText(m_hWnd, szTemp);
#endif

return;
}

When Cosmo is in the embedding state, it appears as shown in Figure 18-2. You'll see that the Import command is still on the File menu and the toolbar.

Figure 18-2.

Cosmo, sporting its embedded object user interface.

Because we modified the appearance of certain menu items, we also need to modify the behavior of those commands. First, to change Save As to Save Copy As, you can either implement a new function or modify your existing save function. In either case, Save Copy As performs the same operation as Save As except that you don't use the filename as the active document or anything to that effect. In other words, you write the file and forget the filename, without changing any other part of your user interface to reflect the filename. It's simply a way for the user to make a disk copy of the object.

In Cosmo, we modify CCosmoDoc::Save so that performing a Save Copy As does not make the document clean, as a typical Save As would. We also do this so that we don't store the filename in the document's structure or change the caption bar. Save determines that we're in an embedding state by calling CFigure::FIsEmbedded (which returns the value of CFigure's m_fEmbedded flag, set to TRUE in IOleObject::SetHostNames). You can see these changes in COSMO\DOCUMENT.CPP.

We also change Exit to Exit And Return To <container document>. There's no big change to the actual process of closing a document and closing the application. But when running normally, Cosmo always checks to see whether the document is dirty when it closes the document before exiting the application. If it is dirty, Cosmo asks the user to save the document to a file.

Because saving the object to a file makes no sense in the case of embedding (we removed File Save altogether), we need to prevent this prompt. So we modify CCosmoDoc::FDirtyGet to return FALSE if we're in the embedded state, which effectively prevents prompting:


BOOL CCosmoDoc::FDirtyGet(void)
{
if (m_pFigure->FIsEmbedded())
return FALSE;

return m_fDirty;
}

Now you are probably asking, "What if the object really is dirty? How do we ensure that the container saves the object before we destroy it?" We need to tell the container to save the object when we're closing the document holding the object by calling IOleClientSite::SaveObject, which is done in the CCosmoDoc destructor:


CCosmoDoc::~CCosmoDoc(void)
{
m_pFigure->SendAdvise(OBJECTCODE_SAVEOBJECT);

§
}

And because I know you're getting sick of seeing this CFigure::SendAdvise function without knowing what it does, it's about time we looked at it and its notifications in general.