Round the Corners: Other Miscellany

To finish up a complete container, we need to make a number of minor modifications, some of which are required and others of which are optional:

The following sections briefly discuss each of these modifications.

Call IOleInPlaceActiveObject::On[Frame ¦ Document]WindowActivate

For the frame window, process the WM_ACTIVATEAPP message and call IOleInPlaceActiveObject::OnFrameWindowActivate if you have a UI-active object from IOleInPlaceFrame::SetActiveObject. The argument to OnFrameWindowActivate is a BOOL indicating whether the frame window is becoming active (TRUE) or inactive (FALSE). You can see a demonstration of this inCPatronFrame::FMessageHook.

Document switching is a little more complex, but it applies only to an MDI application. You need to process the WM_MDIACTIVATE message and check whether there is a UI-active object in the document at all. If there is, you need to call IOleInPlaceActiveObject::OnDocWindowActivate with a BOOL indicating activation (TRUE) or deactivation (FALSE) of the window itself. In response, the object will deactivate or activate its own UI (calling IOleInPlaceSite members accordingly). On the other hand, if a document window is becoming active and does not have a UI-active object in it, the container should reinstate its own frame UI. All of this occurs in CPatronDoc::FMessageHook, which calls CPatronFrame::ReinstateUI when activating a document without a UI-active object. The result is that switching documents will switch the necessary UI, depending on the contents of the documents.

Call IOleInPlaceActiveObject::ResizeBorder

Because a UI-active object initially calls IOleInPlaceUIWindow::GetBorder to obtain the dimensions with which to create its in-place tools, a container has to tell the UI-active object when those dimensions change by calling IOleInPlaceActiveObject::ResizeBorder. The container passes the new size of the window (a RECT), the appropriate IOleInPlaceUIWindow pointer for the frame or document window, and a flag, fFrame, to indicate which window was resized. In response, the object will renegotiate for tool space and resize its tools as necessary. Patron makes these calls from within the WM_SIZE cases of CPatronFrame::FMessageHook and CPatronDoc::FMessageHook, calling GetClientRect to determine the rectangle and passing the other arguments as appropriate.

Be careful with the fFrame argument. When I first wrote this code, my document passed a TRUE and all sorts of wild things started happening as the object was resizing its frame tools to the dimensions of the document window. It was a hard bug to find, so give your implementation a little extra care.

Call IOleInPlaceObject::SetObjectRects and Flush Out IOleInPlaceSite

Remember when we implemented IOleInPlaceSite::GetWindowContext? This function returned initial position and clipping rectangles to the object. If you scroll the document, however, you change these rectangles and must pass new values to all in-place–active objects, not only the UI-active object, by calling IOleInPlaceObject::SetObjectRects for each. Whenever Patron detects that scrolling has occurred, it will call CPage::ScrolledWindow, which loops over all the tenants in the page, calling CTenant::UpdateInPlaceObjectRects in each. This function calculates the new position rectangle based on the current scroll position and calls IOleInPlaceObject::SetObjectRects if that tenant holds an in-place–active object.

CTenant::UpdateInPlaceObjectRects also handles calls that come into IOleInPlaceSite::OnPosRectChange. An object will call this with a rectangle whenever it needs a larger position rectangle in the container. This might be in response to the user resizing the object with its own grab handles or to the object executing some command to change a scaling factor.

You can handle this in the container in two ways. Both methods end in a call to IOleInPlaceObject::SetObjectRects once again. First, you can restrict the object's size to the size of the site or to whatever other limit you want to enforce. In this case, you'll call SetObjectRects again with the rectangle of your choice. The object can choose to display (or remove) scrollbars as adornments, or it can scale the object as it sees fit. The other method is to let the object grow by passing whatever rectangle the object passed to you back to IOleInPlaceObject::SetObjectRects. In any case, the container controls the space the object occupies in the container, but the object controls how the object decides to use that space for scaling or whatever.

Besides the end user scrolling the document directly, the object can ask the container to scroll programmatically by calling IOleInPlaceSite::Scroll. This is useful when the object knows that it is clipped by the document window but the end user has done something to indicate that he or she would like to see that part of the object. Thus, Patron implements this function as follows:


STDMETHODIMP CImpIOleInPlaceSite::Scroll(SIZE sz)
{
int x, y;

x=m_pTen->m_pPG->m_xPos+sz.cx;
y=m_pTen->m_pPG->m_yPos+sz.cy;

SendScrollPosition(m_pTen->m_hWnd, WM_HSCROLL, x);
SendScrollPosition(m_pTen->m_hWnd, WM_VSCROLL, y);
return NOERROR;
}

The SendScrollPosition macros (INC\BOOK1632.H) here cause the document to scroll, which will generate calls to IOleInPlaceObject::SetObjectRects once again.

Implement Minimal Context-Sensitive Help Support

Even if a container does not understand context-sensitive help, it must support an object that does. This means that a container must implement accelerators for Shift+F1 (enter mode) and Esc (exit mode) and process them as follows. (fEnterMode is TRUE on Shift+F1 and FALSE on Esc.)

Call your own IOleInPlaceFrame::ContextSensitiveHelp(fEnterMode).

If your application is an SDI application, have the frame implementation call IOleInPlaceActiveObject::ContextSensitiveHelp(fEnterMode).

If your application is an MDI application, have the frame implementation call IOleInPlaceUIWindow::ContextSensitiveHelp(fEnterMode) for each document. Patron does this in CPatronClient::CallContextHelpOnDocuments.

For MDI documents, have their IOleInPlaceUIWindow implementations call IOleInPlaceActiveObject::ContextSensitiveHelp(fEnterMode).

If the object detects Shift+F1 or Esc, it will call your IOleInPlaceSite::ContextSensitiveHelp, which should be implemented as follows:

There are some special considerations for an application that is both a container and a server and that has its own object embedded within itself. See the OLE Programmer's Reference for more details.

Provide Minimal Undo Support

Even if the container does not have an Undo command (à la Patron), it still has two small responsibilities in that regard. First, it must implement IOleInPlaceSite::DeactivateAndUndo by calling IOleInPlaceObject::InPlaceDeactivate:


STDMETHODIMP CImpIOleInPlaceSite::DeactivateAndUndo(void)
{
m_pTen->m_pIOleIPObject->InPlaceDeactivate();
return NOERROR;
}

Second, whenever the object is deactivated, the container should call IOleObject::DoVerb with OLEIVERB_DISCARDUNDOSTATE. Why do we do this? Read on. It's important if you have an Undo command of your own—or if you are just exceptionally curious.

Support Your Own Undo Command

Applications that support Undo operations generally save some Undo information whenever a change takes place. An application such as this will have this state when it activates an in-place object. If the first thing a user does afterward is choose an Undo command, the object will call your IOleInPlaceSite::DeactivateAndUndo. At this time, you deactivate the object and undo the last change in the container. You do this because activation and deactivation are not considered separate undoable actions, so the container reverses the last action that occurred before the Undo. If the user makes any changes to the object, it will call the container's IOleInPlaceSite::DiscardUndoState, at which time you can discard the last change state. Thus you hold the Undo state only as long as needed.

Now let's go the other direction. Suppose we just deactivated an object in a container and the user chooses Undo. We need to undo the deactivation and tell the object to undo its last change (which is one reason the object remains running after being deactivated). So the container calls IOleInPlaceObject::ReactivateAndUndo. If you make some other change before the Undo occurs, you have to tell the object to discard whatever Undo state it might have by calling IOleObject::DoVerb(OLEIVERB_DISCARDUNDOSTATE).

The fUndoable flag passed to IOleInPlaceSite::OnUIDeactivate tells the container whether the object supports this sort of Undo at all.

Provide an Open Accelerator

If you want, you can add a Ctrl+Enter accelerator for in-place activation that would generate a call to IOleObject::DoVerb(OLEIVERB_OPEN) to take an object from the in-place–active state to the open state. The object will generally also provide a way to do this on its own menus, so don't add a menu item, simply add the accelerator. Of course, you can use this accelerator all the time if you want, even outside in-place activation.

Implement IOleInPlaceFrame::SetStatusText

If the container has a status line, it remains visible all through an in-place session. You also need to give the UI-active object a way to display status information. This is the purpose of IOleInPlaceFrame::SetStatusText, in which you take whatever text the object gives and display it in your status line, as Patron does here:


STDMETHODIMP CPatronFrame::SetStatusText(LPCOLESTR pszText)
{
m_pSL->MessageSet(pszText);
return NOERROR;
}

If an object calls SetStatusText with NULL, it is asking whether you have a status line at all. If you don't, return E_FAIL, which allows the object to display its own status line as an in-place tool.

Show or Hide Modeless Pop-Up Windows

If a container has any modeless pop-up windows, an object might ask that you hide or disable them (your choice) by calling IOleInPlaceFrame::EnableModeless(FALSE). It will later call this function with TRUE to show and enable those same windows. You can ask the object to do the same through IOleInPlaceActiveObject::EnableModeless, typically when you display some modal dialog box in which you want to disable anything else that appears to be part of the container. So both EnableModeless functions allow either container or object to enter a modal state and have that state apply to both.