Implement Simple Activation and Deactivation

As described in Chapter 22, IOleObject::DoVerb is the function in which in-place activation starts. First, then, we need to decide which verbs will try to activate an object in place. For example, a Play verb will not need to activate an object in place because it can temporarily play in the container already. Merging the user interface isn't necessary. We'll need to define the behavior for all our supported verbs as well as any standard ones according to Table 23-1.

Verb

Behavior

OLEIVERB_PRIMARY

Attempt full in-place UI activation if appropriate for this object.

OLEIVERB_SHOW

Attempt full in-place UI activation. On failure, open the object in a server window as usual.

OLEIVERB_HIDE

Fully deactivate the object if it's currently active in place; otherwise, hide the server window as usual.

OLEIVERB_OPEN

Do not attempt to activate in place. Always open in a server window.

OLEIVERB_INPLACEACTIVATE

Activate in place without UI activation, or fail if in-place activation by itself is not possible.

OLEIVERB_UIACTIVATE

Activate the object UI. (Implies activation in place first.)

OLEIVERB_DISCARDUNDOSTATE

Free any Undo information you are holding. (See "Provide Undo Support" on page 1095.)


Table 23-1.

In-place–activation behavior for standard verbs.

We can see how Cosmo handles these in its DoVerb implementation:


STDMETHODIMP CImpIOleObject::DoVerb(LONG iVerb, LPMSG pMSG
, LPOLECLIENTSITE pActiveSite, LONG lIndex, HWND hWndParent
, LPCRECT pRectPos)
{

§
switch (iVerb)
{
case OLEIVERB_HIDE:
if (NULL!=m_pObj->m_pIOleIPSite)
m_pObj->InPlaceDeactivate();
else
{
ShowWindow(hWnd, SW_HIDE);
m_pObj->SendAdvise(OBJECTCODE_HIDEWINDOW);
}
break;

case OLEIVERB_PRIMARY:
case OLEIVERB_SHOW:
//If already in-place active, nothing much to do here.
if (NULL!=m_pObj->m_pIOleIPSite)
return NOERROR;

if (m_pObj->m_fAllowInPlace)
{
if (SUCCEEDED(m_pObj->InPlaceActivate(pActiveSite
, TRUE))) return NOERROR;
}

//FALL-THROUGH

case OLEIVERB_OPEN:
/*
* If already in-place active, deactivate and
* prevent later reactivation.
*/
if (NULL!=m_pObj->m_pIOleIPSite)
{
m_pObj->InPlaceDeactivate();
m_pObj->m_fAllowInPlace=FALSE;
}

/*
* With all in-place stuff gone, we can go back to
* our normal open state.
*/
ShowWindow(hWnd, SW_SHOWNORMAL);
SetFocus(hWnd);

m_pObj->SendAdvise(OBJECTCODE_SHOWOBJECT);
m_pObj->SendAdvise(OBJECTCODE_SHOWWINDOW);
break;

case OLEIVERB_INPLACEACTIVATE:
return m_pObj->InPlaceActivate(pActiveSite, FALSE);

case OLEIVERB_UIACTIVATE:
return m_pObj->InPlaceActivate(pActiveSite, TRUE);

case OLEIVERB_DISCARDUNDOSTATE:
//This program doesn't hold a state, but if yours does,
//free it here.
break;

default:
return ResultFromScode(OLEOBJ_S_INVALIDVERB);
}

return NOERROR;
}

If Cosmo fails to activate in place for OLEIVERB_SHOW or OLEIVERB_PRIMARY, it tries to open the object in a window as usual. This always happens with OLEIVERB_OPEN, so opening an object in a separate window from an in-place–active state is tricky because it requires deactivation first, after which we can show Cosmo's main window. After we're open in the main window, we cannot return to the in-place–active state unless the user closes the window and reactivates the object in the container. We must also guard against additional calls to DoVerb while we're open so that we don't attempt to become in-place active again. The m_fAllowInPlace variable controls this.

Now, to make any of these verbs work as advertised, we need to do a little work in our InPlaceActivate and InPlaceDeactivate helper functions.

Basic In-Place Activation (sans UI)

The steps for basic activation are similar to steps we used in Chapter 22:

Using the IOleClientSite pointer passed to IOleObject::DoVerb, query for IOleInPlaceSite. If that interface is available, call IOleInPlaceSite::CanInPlaceActivate. If that call succeeds, call IOleInPlaceSite::OnInPlaceActivate.

Call IOleInPlaceSite::GetWindowContext, use the window context returned to change the parent and position of your editing window, show the window in the container, and call IOleClientSite::ShowObject.

Call IOleInPlaceFrame::SetActiveObject and IOleInPlaceUIWindow::SetActiveObject to give the container your IOleInPlaceActiveObject pointer.

If you want to fully activate the UI as well, call your own UIActivate helper function. The function does nothing at this point, but you'll want to call it later, so you might as well add it now while you're thinking about it.

You can see these steps implemented in Cosmo's CFigure::InPlaceActivate:


HRESULT CFigure::InPlaceActivate(LPOLECLIENTSITE pActiveSite
, BOOL fIncludeUI)
{
HRESULT hr;
HWND hWnd, hWndHW;
RECT rcPos;
RECT rcClip;

if (NULL==pActiveSite)
return ResultFromScode(E_INVALIDARG);

//If already active, activate UI and we're done.
if (NULL!=m_pIOleIPSite)
{
if (fIncludeUI)
UIActivate();

return NOERROR;
}

hr=pActiveSite->QueryInterface(IID_IOleInPlaceSite
, (PPVOID)&m_pIOleIPSite);

if (FAILED(hr))
return hr;

hr=m_pIOleIPSite->CanInPlaceActivate();

if (NOERROR!=hr)
{
m_pIOleIPSite->Release();
m_pIOleIPSite=NULL;
return ResultFromScode(E_FAIL);
}

m_pIOleIPSite->OnInPlaceActivate();
m_fUndoDeactivates=TRUE;

m_pIOleIPSite->GetWindow(&hWnd);
m_pFR->m_frameInfo.cb=sizeof(OLEINPLACEFRAMEINFO);

m_pIOleIPSite->GetWindowContext(&m_pIOleIPFrame
, &m_pIOleIPUIWindow, &rcPos, &rcClip
, &m_pFR->m_frameInfo);

m_pFR->m_pIOleIPFrame=m_pIOleIPFrame;

m_pHW->HwndAssociateSet (m_pFR->Window());
m_pHW->ChildSet(m_pPL->Window()); //Calls SetParent
m_pHW->RectsSet(&rcPos, &rcClip); //Positions Polyline

hWndHW=m_pHW->Window();
SetParent(hWndHW, hWnd); //Move hatch window.
ShowWindow(hWndHW, SW_SHOW); //Make us visible.
SendAdvise(OBJECTCODE_SHOWOBJECT);

if (fIncludeUI)
return UIActivate();

return NOERROR;
}

As we discussed in Chapter 22, the query for IOleInPlaceSite and the call to IOleInPlaceSite::CanInPlaceActivate are made to ensure that the container is both in-place capable and willing to activate this particular object in place. Failure in either case means that we activate the object in a separate window, remaining fully compatible with non-in-place containers. Success in Cosmo means that CFigure::m_pIOleIPSite is set to a non-NULL value. This variable is used in other places around the code to determine whether we're in-place activated.

If we can proceed to in-place activation, we call IOleInPlaceSite::OnInPlaceActivate so that it can initialize whatever state it wants. We also want to remember that the next Undo command we see should deactivate us, so we set our m_fUndoDeactivates variable to TRUE.

Our next task is to get all the information we need to work with the container by calling IOleInPlaceSite::GetWindow, which will be the parent of our editing window, and by calling IOleInPlaceSite::GetWindowContext. The call to GetWindowContext provides us with the position rectangle that the object's data area should occupy, a clipping rectangle, the container's IOleInPlaceFrame and IOleInPlaceUIWindow pointers, and the container's OLEINPLACEFRAMEINFO structure, which holds the container's accelerators.1 We'll need this information in our frame's message loop later on, so Cosmo stores this filled structure directly in its CCosmoFrame (m_pFR) object along with the container's IOleInPlaceFrame pointer, which our frame will need as well.

Next we need to take our editing window, which in Cosmo is the Polyline window referenced through CFigure::m_pPL, and move it to the container as a child of whatever window we get from IOleInPlaceSite::GetWindow. We don't actually place the Polyline window in the container alone: the Polyline window is a child of a special hatch window that is implemented in the sample code's CLASSLIB framework as CHatchWin (CLASSLIB\CHATCH.CPP). CFigure creates a CHatchWin object in CFigure::Init, storing the pointer in m_pHW for our use here. The hatch window itself doesn't send any messages to its parent window but rather to an "associate" that we set with CHatchWin::AssociateSet. This ensures that our in-place window will not send spurious messages to the container.

This hatch window, in its WM_PAINT processing, draws the border hatching around the object (as described in Chapter 22) using our INOLE.DLL helper function UIDrawShading (INOLE\UIEFFECT.CPP). The width of the border is read from the OleInPlaceBorderWidth entry in WIN.INI (with a default of 4 pixels). In addition, the hatch window manages the position and the clipping rectangles that we obtain from the container. Basically, we make the Polyline window a child of the hatch window (CHatchWin::ChildSet). Then we tell the hatch window through CHatchWin::RectsSet to size the Polyline window within it to the position rectangle (rcPos) and to size itself according to the intersection of the position rectangle and the clipping rectangle (prcClip):


//From CLASSLIB\CHATCH.CPP
void CHatchWin::RectsSet(LPRECT prcPos, LPRECT prcClip)
{
RECT rc;
RECT rcPos;

//Calculate rectangle for hatch window; then clip it.
rcPos=*prcPos;
InflateRect(&rcPos, m_dBorder, m_dBorder);
IntersectRect(&rc, &rcPos, prcClip);

SetWindowPos(m_hWnd, NULL, rc.left, rc.top, rc.right-rc.left
, rc.bottom-rc.top, SWP_NOZORDER ś SWP_NOACTIVATE);

/*
* Set rectangle of child window to be at m_dBorder
* from top and left but with same size in prcPos.
* The hatch window will clip it.
*/
SetWindowPos(m_hWndKid, NULL, rcPos.left-rc.left+m_dBorder
, rcPos.top-rc.top+m_dBorder, prcPos->right-prcPos->left
, prcPos->bottom-prcPos->top, SWP_NOZORDER ś SWP_NOACTIVATE);

return;
}

The cumulative effect is that the object window is always scaled to the position rectangle but at the same time the position rectangle is clipped to the window's parent. (The hatch window has WS_CLIPCHILDREN for this purpose.) As the hatch window clips itself to the clipping rectangle, the object is also clipped to the clipping rectangle while still showing the proper scaling. The hatch window keeps itself slightly larger than its child window by the hatch border width on all sides, subject, of course, to the container's clipping rectangle.

After the hatch window is positioned with the Polyline window inside it, we can move it to the container with SetParent, make it visible with ShowWindow, and call IOleClientSite::ShowObject to let the container know our application is visible. This last call allows the container to scroll our application into view if it is scrolled out of view at the time.

We should now also implement both IOleInPlaceObject::GetWindow and IOleInPlaceActiveObject::GetWindow to return the topmost window that was moved into the container. In Cosmo, this is the hatch window:


STDMETHODIMP CImpIOleInPlaceObject::GetWindow(HWND FAR *phWnd)
{
*phWnd=m_pObj->m_pHW->Window();
return NOERROR;
}

STDMETHODIMP CImpIOleInPlaceActiveObject::GetWindow(HWND FAR *phWnd)
{
*phWnd=m_pObj->m_pHW->Window();
return NOERROR;
}

This works well because when the object is UI active and the container gets a WM_SETFOCUS in the frame, the container calls SetFocus on whatever window is returned from the UI-active object's IOleInPlaceActiveObject::GetWindow. That means it will call SetFocus on the hatch window, which will call SetFocus on our editing window. This is exactly how things should happen.

As the final step, CFigure::InPlaceActivate calls CFigure::UIActivate if we're going that far. We'll see what happens here shortly.


Object Adornments

The hatch window is a special case of what are called object adornments, or additional user interface elements that appear outside the position rectangle of the object but inside the clipping rectangle. A spreadsheet object might display row and column headings, for example. The position rectangle defines the space that the object's data area should occupy, so adornments are not affected by that rectangle. If you need additional space for similar adornments, you can expand whatever windows you place in the container to accommodate them as long as those windows stay within the container's clipping rectangle and the object area stays the same size as the position rectangle, at least initially. No matter what adornments you add, the hatched border should always surround the object and all adornments.


Basic Deactivation (sans UI)

Deactivation basically means reversing anything we did in the activation phase, in the opposite order. Where we previously obtained an interface pointer, we release it here. Where we called SetParent to move a window to the container, we call SetParent here to bring it back to the server. Deactivation involves the following steps:

Call the UIDeactivate helper function to remove menus and tools.

Call SetParent to move the hatch window and editing window back to the server.

Call IOleInPlaceSite::OnInPlaceDeactivate, release the pointers obtained from IOleInPlaceSite::GetWindowContext, and release the IOleInPlaceSite pointer obtained from QueryInterface.

We can see these steps implemented in the code for Cosmo's CFigure::InPlaceDeactivate:


void CFigure::InPlaceDeactivate(void)
{
RECT rc;

UIDeactivate();

SetParent(m_pPL->Window(), m_pDoc->m_hWnd);
m_pHW->ChildSet(NULL);

//Be sure the hatch window is invisible and owned by Cosmo.
ShowWindow(m_pHW->Window(), SW_HIDE);
SetParent(m_pHW->Window(), m_pDoc->m_hWnd);
GetClientRect(m_pDoc->m_hWnd, &rc);
InflateRect(&rc, -8, -8);

SetWindowPos(m_pPL->Window(), NULL, rc.left, rc.top
, rc.right-rc.left, rc.bottom-rc.top
, SWP_NOZORDER ś SWP_NOACTIVATE);

if (NULL!=m_pIOleIPSite)
m_pIOleIPSite->OnInPlaceDeactivate();

m_pFR->m_pIOleIPFrame=NULL;
ReleaseInterface(m_pIOleIPFrame);
ReleaseInterface(m_pIOleIPUIWindow);
ReleaseInterface(m_pIOleIPSite);

return;
}

When we set Cosmo's Polyline window as a child of the hatch window, we repositioned it in relation to its parent. When we move it back to Cosmo's document window now, we have to adjust this relative position again. If we're deactivating because of OLEIVERB_OPEN, we'll be showing the document window in the server, so the Polyline window better appear in the right place!

Also, in-place deactivation takes the object back only to the running state, not to the loaded state. That means that a local server such as Cosmo will still be running. (An in-process server such as the Polyline sample in this chapter will always be in memory anyway.) Because the object has not been destroyed, you cannot assume that in-place activation will give you a chance to reinitialize your object's variables. So you need to be sure they are set back to whatever state you expect in IOleObject::DoVerb. For Cosmo, we set the m_pIOleIPSite pointer to NULL so that we can activate in place again.

At this point in our implementation, we could compile our code, test it with a container, and see many of the in-place–activation steps take place. When you click outside the object in the container, you can trace through the deactivation steps.2 What's missing are the extra pieces of user interface, which we'll now build in Cosmo.

1 The object must fill the cb field of the OLEINPLACEFRAMEINFO structure before calling GetWindowContext to identify the version of the structure desired.

2 As an extra debugging measure, you might also display your server window during this time so that you can watch the editing window disappear from within the server document on activation and reappear on deactivation.