We're now on the home stretch and have only to add little bits of code to complete our in-place object implementation. Some of the following steps are required, and some are optional:
(Required) Resize your tools on IOleInPlaceActiveObject::ResizeBorder.
(Required) Implement IOleInPlaceObject::SetObjectRects to update your editing window's position, and call IOleInPlaceSite::OnPosRectChange and IOleInPlaceSite::Scroll if you need more room.
(Required) Implement IOleInPlaceActiveObject::OnFrameWindowActivate and IOleInPlaceActiveObject::OnDocWindowActivate to handle UI changes appropriately.
(Required) Implement minimal context-sensitive help support in case the container supports it, even if you don't.
(Required) Provide Undo support by calling IOleInPlaceSite::DiscardUndoState when making a change after activation and by implementing IOleInPlaceObject::ReactivateAndUndo.
(Optional) If you have an Undo command, call IOleInPlaceSite::DeactivateAndUndo if the command is given immediately after deactivation. Also implement IOleObject::DoVerb for OLEIVERB_DISCARDUNDOSTATE.
(Optional) Call IOleInPlaceFrame::SetStatusText with messages for your menu items even if you don't have your own status line.
(Optional) Provide methods for opening the object into a server window from an in-place–active state.
(Optional) If you have modeless pop-up windows that are usually shown as part of your user interface, show/hide or enable/disable these windows when your IOleInPlaceActiveObject::EnableModeless function is called. You can also tell the container to do the same by calling IOleInPlaceFrame::EnableModeless.
Also, if you want to experiment with your object as an inside-out object, mark it with OLEMISC_INSIDEOUT and OLEMISC_ACTIVATEWHENVISIBLE (I've done the latter for the Polyline sample in this chapter) and then run the object with a container that supports inside-out objects. You'll see that such a container treats your application somewhat differently when it's marked with these bits, especially the latter bit, which is common with OLE controls.
Let's now look at each of these steps in more detail.
Whenever an end user resizes the container's frame window or document window, the container calls your IOleInPlaceActiveObject::ResizeBorder function with the IOleInPlaceUIWindow interface for the container's frame or document.6 A flag indicates which one the pointer refers to. Within ResizeBorder, you must renegotiate for space for your object's tools and resize those tools to fit in that space:
STDMETHODIMP CImpIOleInPlaceActiveObject::ResizeBorder(LPCRECT pRect
, LPOLEINPLACEUIWINDOW pIUIWindow, BOOL fFrame)
{
//The document case is uninteresting to us.
if (!fFrame)
return NOERROR;
if (!m_pObj->InPlaceToolsRenegotiate())
return ResultFromScode(INPLACE_E_NOTOOLSPACE);
SetWindowPos(m_pObj->m_pTB->Window(), NULL, pRect->left, pRect->top
, pRect->right-pRect->left, m_pObj->m_cyBar, SWP_NOZORDER);
return NOERROR;
}
This code from Cosmo first shows that as a result of not having any document tools, we can simply return NOERROR if the fFrame parameter is FALSE. Otherwise, we need to go through the RequestBorderSpace and SetBorderSpace process again, which is handled by CFigure::InPlaceToolsRenegotiate. To optimize, you can try calling SetBorderSpace first; only if that fails do you need to call RequestBorderSpace again. Either way works—containers expect this.
When you've renegotiated for space, reposition your tools by using the rectangle passed to this function. This rectangle contains the same thing you would receive from a call to the container's GetBorder function.
There will probably be situations in which your object is not entirely visible and the end user will want to see more of the object. This can occur in two ways. The end user might scroll the container document directly or might perform some action in your object that would require your application to bring more of itself into view.
When the user scrolls the container document, the container calls IOleInPlaceObject::SetObjectRects with a new position rectangle and a new clipping rectangle. The object must resize its editing window to the new position rectangle, keeping it clipped to the clipping rectangle. Cosmo tells its hatch window to do this, using CHatchWin::SetObjectRects, as we saw earlier:
STDMETHODIMP CImpIOleInPlaceObject::SetObjectRects(LPCRECT prcPos
, LPCRECT prcClip)
{
m_pObj->m_pHW->RectsSet((LPRECT)prcPos, (LPRECT)prcClip);
return NOERROR;
}
When IOleObject::SetObjectRects is called, you have no choice but to obey the restrictions of the container. However, that does not mean that you have no control over the size of your object. The object can itself provide grab handles in the hatch border (which are capable of producing mouse events that would show up in the hatch window's message procedure). If the user resizes the object in this way, you can expand the editing window to show more data. (This does not affect the container's site size, however.) When an object is resized in this way, calculate the new position rectangle of the editing area of the window—making sure that you exclude all object adornments and the hatch border—and call IOleInPlaceSite::OnPosRectChange. In this function, the container will determine whether it can give you this space, or at least a portion of it, and will call your IOleInPlaceObject::SetObjectRects in response. At that time, your application obediently resizes itself. Some containers will let your object grow as much as it wants. Others will never let it grow larger than the container site itself. It's your responsibility to determine how your application will respond in these cases. You can either scale your object to fit the container's specified position rectangle or add scroll bars to your in-place editing window if you cannot scale and cannot display all the object's data in the position rectangle. In any case, what you do with the position rectangle from SetObjectRects is your choice, as long as your object can fit into it somehow.
Scrolling can also occur when part of your object's window is clipped by the container's windows. For example, a table object with only half the cells visible might be clipped by the edge of the container's document window. The user might use the arrow keys to move the cell selection into one of the hidden cells. In that case, your object can call IOleInPlaceSite::Scroll to scroll the document programmatically. If the container scrolls, it will again call IOleObject::SetObjectRects to tell the object its new position.
When an object is UI active in a container document, it has menus and tools displayed in the container's frame window. If that container has multiple documents, the object's user interface should no longer appear in the frame when the user switches to another document. Therefore, the container might need to ask an object to temporarily remove its UI or to reinstall it when document activation changes. For this reason, IOleInPlaceActiveObject::OnDocWindowActivate is passed a BOOL indicating whether the document is becoming active or inactive. In response, the object must either show or hide its UI-active state:
STDMETHODIMP CImpIOleInPlaceActiveObject::OnDocWindowActivate
(BOOL fActivate)
{
HWND hWndTB;
if (NULL==m_pObj->m_pIOleIPFrame)
return NOERROR;
hWndTB=m_pObj->m_pTB->Window();
if (fActivate)
{
m_pObj->m_pIOleIPFrame->SetActiveObject(this
, (*m_pObj->m_pST)[IDS_INPLACETITLE]);
m_pObj->m_pIOleIPFrame->SetMenu(m_pObj->m_hMenuShared
, m_pObj->m_hOLEMenu, m_pObj->m_pFR->Window());
if (m_pObj->InPlaceToolsRenegotiate())
{
RECT rc;
m_pObj->m_pIOleIPFrame->GetBorder(&rc);
SetWindowPos(hWndTB, NULL, rc.left, rc.top
, rc.right-rc.left, m_pObj->m_cyBar
, SWP_NOZORDER);
ShowWindow(hWndTB, SW_SHOW);
}
}
else
{
m_pObj->m_pIOleIPFrame->SetActiveObject(NULL, NULL);
//Hide our tools, but do not call SetMenu.
ShowWindow(hWndTB, SW_HIDE);
}
return NOERROR;
}
The implementation of this function need not perform full UI deactivation or UI activation. To hide our UI, we simply need to call the frame's SetActiveObject with NULLs and hide our own tools. The container will then reinstate its tools, or if the document that is becoming active also has a UI-active object, that other object will reinstate its own tools. Hiding the object's UI requires no call to IOleInPlaceFrame::SetMenu because either the container will show its own UI or another object will display its shared menu.
When asked to show the object's UI, we call SetActiveObject again and reinstall the shared menu (which we saved in CFigure variables for this exact reason). We must also negotiate space for our tools again because conditions might have changed in the container since we were last active and the container might now refuse our tools.
Closely related to document switches is the function IOleInPlaceActiveObject::OnFrameWindowActivate, which tells the active object that the container's frame window has become active or inactive. An object has no set requirements here, but it can use this notification to perhaps show or hide modeless pop-up windows or control the activation of other user interface elements.
Both containers and objects must provide at least some support for context-sensitive help even if they do not support that feature themselves. An object that doesn't support this help mode must avoid trapping the Shift+F1 and Esc accelerators—let them pass through to the container. If the container wants to enter the help mode, it will do so and call IOleInPlaceObject::ContextSensitiveHelp(TRUE) in your object. When this help mode is on and you do not support such help, ignore all mouse clicks in your object—that is, provide no help.
If an object does support context-sensitive help, it will trap Shift+F1 and Esc. On Shift+F1, enter the mode and tell the container by calling IOleInPlaceSite::ContextSensitiveHelp(TRUE). When you detect Esc, stop the help mode and tell the container with IOleInPlaceSite::ContextSensitiveHelp(FALSE).
Undo is another function that you might or might not support and your container might or might not support. Even if an object does not support Undo, the object must still reactivate itself in IOleInPlaceObject::ReactivateAndUndo. If your object has an Undo command itself, you not only reactivate yourself but also perform an Undo directly. Cosmo does the reactivation only because its Undo is really a Remove Last Point command, not a true Undo. (Cosmo does not maintain a last command variable to perform an Undo properly.) So it handles this case as follows:
STDMETHODIMP CImpIOleInPlaceObject::ReactivateAndUndo(void)
{
return m_pObj->InPlaceActivate(m_pObj->m_pIOleClientSite, TRUE);
}
An object that supports the Undo command should maintain a flag such as CFigure::m_fUndoDeactivates, which is initially set to TRUE in CFigure::InPlaceActivate. If the user selects Undo when this flag is set, call IOleInPlaceSite::DeactivateAndUndo, which will cause the container to deactivate the object. Cosmo does this in CFigure::Undo, which is called whenever Ctrl+Z or Alt+ Backspace accelerators are detected:
BOOL CFigure::Undo(void)
{
if (!m_fUndoDeactivates)
return FALSE;
m_fUndoDeactivates=FALSE;
m_pIOleIPSite->DeactivateAndUndo();
return TRUE;
}
Cosmo clears m_fUndoDeactivates whenever the user makes any change whatsoever to the figure. This happens in CFigure::SendAdvise just before Cosmo sends off an IAdviseSink::OnDataChange notification, which also happens for any change to the object. In addition, if this is the first change to the object after activation, we must call IOleInPlaceSite::DiscardUndoState to tell the container that we will not be calling DeactivateAndUndo so that it can free any state it's holding.
The object can hold some Undo state itself in case the container calls IOleInPlaceObject::ReactivateAndUndo. (Cosmo does not maintain such a state.) If the container makes a change after deactivation so that it will not call this function, it calls IOleObject::DoVerb(OLEIVERB_DISCARDUNDOSTATE), at which time the object frees the state. DoVerb is used because once an object is deactivated, the container no longer has IOleInPlaceObject or IOleInPlaceActiveObject pointers but will have IOleObject.
The overall effect of this Undo handling is meant to create the illusion that the user is working with a single undo stack in the container, even though objects from other components are being activated. This is why ReactivateAndUndo and DeactivateAndUndo involve both the activation change and the Undo operation—activation changes themselves are not meant to be visually undoable operations.
As described in Chapter 22, the container maintains ownership of the status line throughout in-place sessions. If an object ever needs to display text in that status line, it can pass the string to IOleInPlaceFrame::SetStatusText. Cosmo does this whenever it detects WM_MENUSELECT messages in CCosmoFrame::FMessageHook; the strings, of course, describe the functionality of the menu item in question. Cosmo has its own status line (which is hidden along with the rest of the server's main window) process the message and load the correct string. For an in-place object, we extract this string and send it to the container, making support for this feature quite simple:
BOOL CCosmoFrame::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
, LPARAM lParam, LRESULT FAR *pLRes)
{
TCHAR szText[128];
if (WM_MENUSELECT!=iMsg)
return FALSE;
if (NULL==m_pIOleIPFrame)
return FALSE;
m_pSL->MenuSelect(wParam, lParam);
m_pSL->MessageGet(szText, sizeof(szText));
m_pIOleIPFrame->SetStatusText(szText);
*pLRes=0L;
return TRUE;
}
You can see this interaction at work when you activate a Cosmo figure in place in the version of Patron from Chapter 22.
I mentioned before that a container can call IOleObject::DoVerb(OLEIVERB_OPEN) to end in-place activation and activate an object in a separate window. We saw earlier how Cosmo implements this verb. In addition, Cosmo provides the user with two other ways to do the same thing: through the Open item on the Edit menu and by picking up double clicks in the hatch border around the figure. You can also use a Ctrl+Enter accelerator for this purpose. (Cosmo does not.) In Cosmo, the menu selection and hatch border double click end up in CFigure::OpenIntoWindow, which calls our own DoVerb to deactivate and open into a separate window:
void CFigure::OpenIntoWindow(void)
{
if (NULL!=m_pIOleIPSite)
{
m_fUndoDeactivates=FALSE;
m_pImpIOleObject->DoVerb(OLEIVERB_OPEN, NULL
, m_pIOleClientSite, -1, NULL, NULL);
m_fForceSave=TRUE;
SendAdvise(OBJECTCODE_DATACHANGED);
}
return;
}
We clear m_fUndoDeactivates so that an Undo command in the open Cosmo window will not attempt to in-place deactivate.
When I implemented the Open command in Cosmo, I encountered a couple of problems. The first was that the object would not be saved (through IPersistStorage::Save) after I opened it in a window and closed the window. The problem was that the object was not marked as dirty after such an operation. To correct this, I added the flag m_fForceSave to CFigure and set it to TRUE in CFigure::OpenIntoWindow. That causes the object to appear as dirty when closing, which generates the proper call to IOleClientSite::SaveObject. The other, related, problem was that the container site was blank when I opened the object into a window until that site was repainted. So in CFigure::OpenIntoWindow, I immediately send an OnDataChange notification, which propagates to the container as OnViewChange, causing it to repaint the site.
The final bit to complete an in-place object is to handle IOleInPlaceActiveObject::EnableModeless and call IOleInPlaceFrame::EnableModeless. As mentioned in Chapter 22, the container and the object use each other's EnableModeless to show/enable or hide/disable floating pop-up windows at various points in the in-place session. If you ever have occasion to tell the container to hide its pop-up windows (say, for example, when you display a modal dialog box), call IOleInPlaceFrame::EnableModeless with TRUE when showing the dialog box and with FALSE when closing the dialog box. The container will do the same thing for the same reasons with IOleInPlaceActiveObject::Enable Modeless. The use and implementation of these functions are, however, entirely optional.
6 Remember that IOleInPlaceFrame inherits from IOleInPlaceUIWindow, but do not typecast this IOleInPlaceUIWindow to IOleInPlaceFrame because the additional frame members will not be in the vtable for the interface pointer you receive. Upcasting like this is playing with some serious fire. |