Provide In-Place Accelerators and Focus

As described earlier, a UI-active object always has first crack at accelerators and keystrokes. If the UI-active object is an in-process object, the container's message loop will pick up accelerators first. To accommodate this possibility, the container must call IOleInPlaceActiveObject::TranslateAccelerator in its message loop before translating any of its own accelerators. If the object uses the keystroke, the container must now also use it. Patron handles this in its CPatronFrame::MessageLoop, in which m_hAccel contains its normal accelerators and m_hAccelIP contains those accelerators that are available with a UI-active object:9


WPARAM CPatronFrame::MessageLoop(void)
{
MSG msg;

while (GetMessage(&msg, NULL, 0, 0))
{
HACCEL hAccel=m_hAccel;

//Always give object first crack at translation.
if (NULL!=m_pIOleIPActiveObject)
{
HRESULT hr;

hAccel=m_hAccelIP;
hr=m_pIOleIPActiveObject->TranslateAccelerator(&msg);

//If object translated the accelerator, we're done.
if (NOERROR==hr)
continue;
}
if (!m_pCL->TranslateAccelerator(&msg))
{
//hAccel is either normal or in place.
if (!::TranslateAccelerator(m_hWnd, hAccel, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}

return msg.wParam;
}

If IOleInPlaceActiveObject::TranslateAccelerator does not process the keystroke, we can resume our message loop as usual. This function is set up in such a way that when we're not servicing an in-place object, we use our normal accelerators. When we have an IOleInPlaceActiveObject pointer, however, which is the in-place session flag for Patron's frame, we use the in-place accelerators.

Now, if the object is implemented in a local server, its message loop will pick up the keystroke messages first. The server processes its own accelerators immediately, but if it doesn't use a particular message, it must pass the message to OleTranslateAccelerator along with the OLEINPLACEFRAMEINFO structure it received from our own IOleInPlaceSite::GetWindowContext and the IOleInPlaceFrame pointer obtained at the same time. The OLEINPLACEFRAMEINFO structure holds our container's in-place accelerators, and OleTranslateAccelerator uses this table to determine whether the keystroke matches any accelerator in the table (or one for an MDI Window menu if the structure's fMDIApp flag is set). If a match is found, OleTranslateAccelerator passes the message and the command ID to IOleInPlaceFrame::TranslateAccelerator; otherwise, it returns to the server's message loop with S_FALSE so that the server can process the message.

As the container is given the command ID, implementing IOleInPlaceFrame::TranslateAccelerator is fairly easy: just invoke the correct command. (Patron does this through CPatronFrame::OnCommand.) An MDI container should also call TranslateMDISysAccel to handle the Window menu:


STDMETHODIMP CPatronFrame::TranslateAccelerator(LPMSG pMSG, WORD wID)
{
SCODE sc;

if ((IDM_PAGENEWPAGE <= wID && IDM_PAGELASTPAGE >= wID)
œœ IDM_OPENOBJECT==wID œœ IDM_ENTERCONTEXTHELP==wID
œœ IDM_ESCAPECONTEXTHELP==wID)
{
//wID properly expands to 32 bits.
OnCommand(m_hWnd, (WPARAM)wID, 0L);
sc=S_OK;
}
#ifdef MDI
else if (TranslateMDISysAccel(m_pCL->Window(), pMSG))
sc=S_OK;
#endif
else
sc=S_FALSE;

return ResultFromScode(sc);
}

There is one more step to make accelerators work properly. Whenever there's a UI-active object, the container must ensure that the object has the keyboard focus so that keyboard messages are marked for the correct window. The object will initially take the focus itself when it becomes UI active, but if the user switches away from the container and back again, the container will receive a WM_SETFOCUS message. Window's default processing of this message will set the focus to the frame window, which is not what we want. To set it to the UI-active object, the container must call SetFocus on the window returned from IOleInPlaceActiveObject::GetWindow:


case WM_SETFOCUS:
if (NULL!=m_pIOleIPActiveObject)
{
HWND hWndObj;

m_pIOleIPActiveObject->GetWindow(&hWndObj);
SetFocus(hWndObj);
}

break;

Note: Avoid the temptation to pass the ill-named hWndObj argu-ment from IOleInPlaceFrame::SetMenu to SetFocus because this might be the frame window handle of the object's server. If that were the case, your container would never get the focus at all. If you tried to reactivate your container's frame window, you would see it briefly flash active but then become inactive again when you made this SetFocus call. To bypass any problems, always use the window handle from IOleInPlaceActiveObject::GetWindow.

9 In C++, ::TranslateAccelerator calls the globally named Windows API. We need this because CPatronFrame has a TranslateAccelerator member function that would be called if we left off the :: at the beginning of the function name. Don't feel bad if you didn't know this already—I had to ask other people how to do this when writing this code.