This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


MIND

FAQ

Download the code (3KB)
Scott Roberts

WebBrowser Keystroke Problems
A
lot of people have noticed that when they're hosting the WebBrowser control in MFC, ATL, or standard C++ applications, they sometimes run into trouble with certain keys. These keystroke problems usually occur when typing into intrinsic controls such as text boxes that reside on Web pages loaded by the WebBrowser control. Usually these keys are accelerator keys such as Backspace, Delete, and Tab. The problem is that the intrinsic controls on a Web page do not automatically receive these accelerator keys. When the WebBrowser control receives an accelerator key message, it does not automatically pass it to child controls on a Web page. Therefore, you must somehow let the WebBrowser control know that it should pass these messages to controls on your Web page.     The solution is always the same whether you are hosting the control in MFC, ATL, or standard C++: call the TranslateAcclerator method of the IOleInPlaceActiveObject interface that is implemented by the WebBrowser control. But where and how you should do this is often unclear. Let's see how to work around keystroke problems in MFC, ATL, and standard C++ applications that are hosting the WebBrowser control.

MFC Dialogs

    You can create three types of applications in MFC: dialog-based, single-document interface (SDI), and multiple-document interface (MDI). Each type requires a different solution to the keystroke problems.

    When hosting the WebBrowser control in an MFC dialog-based application, the TranslateAccelerator method is automatically called for you, so you don't run into these problems with keystrokes. But understanding how MFC works when you are hosting the WebBrowser control in a dialog-based application will help you diagnose and solve problems with keystrokes in other situations.

    In an MFC application, the Windows® message pump is managed for you. In a dialog-based application, a method called IsDialogMessage is invoked each time a message enters the loop. Among other things, this function will call TranslateAccelerator for any controls in the dialog. IsDialogMessage will pass the MSG structure that was received from a call to the GetMessage function in the message pump to the TranslateAccelerator method. MFC does this by creating an instance of a class called COccManager that manages all the OLE controls on your dialog. The IsDialogMessage method is a member of the COccManager class.

    The IsDialogMessage method first gets a handle to the window that has the focus. If this window is a control, the TranslateAccelerator method is called on that control to give it a chance to process the message. If the window that has the focus is not a control, IsDialogMessage keeps calling the GetParent method until it finds an ActiveX® control. If it finds a control, it calls TranslateAccelerator on it.

MFC SDI/MDI Applications

    MFC SDI/MDI applications typically run into problems with keystrokes. Unlike dialog-based applications, IsDialogMessage is not automatically called for you in SDI/MDI applications. Therefore, the way to fix these keystroke problems is to call IsDialogMessage yourself. But where do you call it? Each time through the message pump, MFC gives your application a chance to process a message before the message is processed by the default window procedure. To give you a chance to process a message, MFC calls the PreTranslateMessage method, which is a virtual method of the CWnd class. If you implement this method in any of your CWnd-derived classes, it will be called automatically by MFC.

    CWinApp also has a PreTranslateMessage, which means that you can override this in the CWinApp-derived class for your application as well.

    An MFC SDI/MDI application typically has a view class that derives from CView. CView in turn derives from CWnd. Therefore, if you implement PreTranslateMessage in your view class, through the wonders of polymorphism your implementation of PreTranslateMessage will be called by MFC. So to solve the keystroke problems in an MFC SDI/MDI application, all you have to do is call IsDialogMessage from within PreTranslateMessage like this:



 BOOL CMyView::PreTranslateMessage(MSG* pMsg)
 {
     if (IsDialogMessage(pMsg))
         return TRUE;
     else
         return CWnd::PreTranslateMessage(pMsg);
 }
Knowledge Base article Q165074 (http://support.microsoft.com/support/kb/articles/q165/0/74.asp) explains this in detail.

    But sometimes it does not work. For instance, if the user presses Tab, you would expect the focus to be shifted between all controls in your application as well as controls in the WebBrowser window. Say that you have an application with an edit box in which you can enter a URL. If focus is set to that edit box, each time you press Tab the focus will be shifted around in your application window, but focus will never move to controls on the Web page inside of the WebBrowser control window.

    To make this work, it may be necessary to pass the Tab key directly to the WebBrowser control. Call TranslateAccelerator in the PreTranslateMessage method for your CMainFrame class (see Figure 1). ID_URL_NAME is the resource ID of an edit control in the application that is used to enter a URL.

    If you are hosting the WebBrowser control in a DLL, calling TranslateAccelerator is a bit more involved. Please see Knowledge Base article Q175502 (http://support.microsoft.com/support/kb/articles/q175/5/02.asp) for more information.

ATL and Standard C++

    When hosting the WebBrowser control in either an ATL application or one written in standard C++, the solution is sometimes rather simple. All you have to do is query the WebBrowser control for the IOleInPlaceActiveObject interface and call its TranslateAccelerator method. Typically, you call this method in your handler function for the WM_KEYDOWN message. Figure 2 contains ATL and standard C++ code that shows how to call the TranslateAccelerator method to fix the keystroke problem.

    Sometimes your application will not automatically be sent WM_KEYDOWN messages for accelerator keys. In this case, you must manually send this message to your window. Here is a sample message pump that sends all keyboard messages to the window of your application:



 while (GetMessage(&msg, NULL, 0, 0))
 {
    TranslateMessage(&msg);
 
    // Send all keyboard messages to the window of your
    // application.  hwndApp is the window handle of
    // your application.
    //
    if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST)
       ::SendMessage(hwndApp, msg.message, msg.wParam, msg.lParam);
          
    DispatchMessage(&msg);
 }

Win32 SDK Modal Dialogs

    The Win32® dialog box functions (DialogBox, DialogBoxIndirect, DialogBoxIndirectParam, and DialogBoxParam) are very helpful when creating modal dialog boxes. They take care of handling the message pump for your application. However, this creates a problem when you are trying to fix these keystroke problems. Where do you put the call to TranslateAccelerator?

    Unfortunately, if you need to host the WebBrowser control in a dialog, it is not a good idea to use these functions to create the modal dialog. The reason for this is simple. When focus is set to a control on a dialog, the control is sent the WB_GETDLGCODE message. Controls typically respond to this message by returning DLGC_WANTALLKEYS. Then the control is given a chance to handle all keys entered by the user.

    The WebBrowser control returns DLGC_WANTARROWS| DLGC_WANTCHARS in response to the WM_GETDLGCODE message. This means that it will not handle certain keys such as Tab and Delete. Therefore, to work around these keystroke problems, you need to have control of the message pump so that you can call TranslateAccelerator.

For these reasons, I recommend that you do not use the Win32 dialog box functions to create your modal dialog box. Create the dialog window yourself so you have control of the message pump. You can use MFC or ATL to create this dialog. In addition, if you just need a modal dialog that displays a Web page, you can use the DHTML showModalDialog function provided by Microsoft Internet Explorer.

    There is one other option you can use to fix these problems, instead of creating the dialog window manually. You can use a Windows hook. This will enable you to retrieve all keyboard messages for the current thread and then call TranslateAccelerator so that accelerator keys will be processed. There is one problem with this approach, however. When the focus is on the WebBrowser control and you attempt to change focus between controls on the Web page by pressing tab, the focus will never leave the WebBrowser window. This means that you can Tab between controls on the Web page or between controls in your application, but not both.

There are four steps required to set up a Windows hook to work around the keystroke problems in a Win32 SDK dialog. First, declare your hook procedure in your header file.



 static LRESULT CALLBACK GetMsgHookProc(int nCode, WPARAM wParam, LPARAM lParam);
Next, set your hook procedure during initialization by calling SetWindowsHookEx. Also, make sure to save the returned hook handle so that you can unhook the procedure when you are shutting down or when it is no longer needed.


 // Declare this global handle in one of your project files.
 HHOOK g_hook;
 
 // Place this code inside an initialization 
 // method in your implementation file (.cpp)
 g_hook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgHookProc,
                           NULL, GetCurrentThreadId());
After that, implement your hook procedure and call TranslateAccelerator.

 
 LRESULT CALLBACK CYourClass::GetMsgHookProc(int nCode, WPARAM wParam,
                                             LPARAM lParam)
 {
    LPCKFSEARCH pThis = (LPCKFSEARCH)GetWindowLong(hwndMain, DWL_USER);
 
    if (pThis && nCode >= 0)
    {
       MSG* pMsg = (MSG*)lParam;
 
       // m_pOleInPlaceActObj is an IOleInPlaceActiveObject
       // data member of the view class that is initialized
       // after the WebBrowser control is loaded.
 
       if (pThis->m_pOleInPlaceActObj)
          pThis->m_pOleInPlaceActObj->TranslateAccelerator(pMsg);
 
       // This causes the tab to work in the WebBrowser window. If you do not do
       // this, tabbing will happen in the dialog only.  You have the choice of
       // tabbing in the dialog or the WebBrowser window, not both.
 
       if (pMsg->wParam == VK_TAB)
          ZeroMemory(pMsg, sizeof(MSG));
    }
 
    return CallNextHookEx(g_hook, nCode, wParam, lParam);
 }
Finally, when your application is shutting down or when you no longer need the hook, unhook the procedure.


 UnhookWindowsHookEx(g_hook);

Explorer Bars

    When hosting the WebBrowser control in Explorer bars (Explorer bands, comm bands, or desk bands), you need to do a little more to fix keystroke problems. You still have to call TranslateAccelerator, but there are also issues regarding focus. The WebBrowser control has to know that your browser band currently has the focus or the accelerator keys will not be processed.

    It is very common for people to forget that they have to implement IOleControlSite when hosting a control. This is true when hosting the WebBrowser control in a standard application or when hosting it in an Explorer bar. You must implement this interface because its OnFocus method is called when focus is set to the WebBrowser control. When this method is called, you must query the WebBrowser control for the IInputObjectSite interface and call its OnFocusChangeIS method. This tells the WebBrowser control that your Explorer bar now has the focus.

Here is some ATL-based code that demonstrates how to call the OnFocusChangeIS method:



 STDMETHODIMP CWBExplorerBar::OnFocus(BOOL fGotFocus)
 {
    if (m_pSite)
       m_pSite->OnFocusChangeIS(static_cast<IInputObject*>(this), fGotFocus);
       return S_OK;
 }
    Note that m_pSite is a data member of type IInputObjectSite that was retrieved from the WebBrowser control in the Explorer bar's IObjectWithSite::SetSite method. Also, note that the Explorer bar has to implement the IInputObject interface.

    Now, whenever a key is pressed, three things occur. First, the Explorer bar's IInputObject::HasFocusIO method is called to see if the Explorer bar currently has the focus. The Explorer bar's implementation of this method should determine if the WebBrowser control that the Explorer bar is hosting has the focus or if any of the WebBrowser control's children have the focus. The HasFocusIO method should return S_OK if the WebBrowser control or one of its children has the focus; otherwise it should return S_FALSE. Here is the code for this method.



 STDMETHODIMP CWBExplorerBar::HasFocusIO(void)
 {
    HWND hwnd = GetFocus();
    HWND hwndTmp = m_hwndWB;  // HWND of the WebBrowser control
 
    // See if the focus has been set to any of the children
    while (hwnd && hwndTmp)
    {
       if (hwnd == hwndTmp) return S_OK;
       hwndTmp = ::GetWindow(hwndTmp, GW_CHILD);
    }
 
    return S_FALSE;
 }
    The Explorer bar's IInputObject::UIActivateIO method is called next to tell the Explorer bar that it is being activated. The Explorer bar's implementation of this method either UI activates or in-place activates the WebBrowser control depending on one of the input values to this method. In Figure 3, m_pIOleObject is a pointer to an instance of IOleObject that was retrieved from the WebBrowser control in the SetSite method of this Explorer bar.

    Finally, the Explorer bar's IInputObject::TranslateAcceleratorIO method is called. Here the Explorer bar passes the keystroke to the hosted WebBrowser control by calling IOleInPlaceActiveObject::TranslateAccelerator. This causes accelerators such as the Backspace and the Delete keys to be processed by the WebBrowser control (see Figure 4).

    The steps and associated code that I just showed you may be a little confusing at first. To better understand how browser bands work and how to deal with keystroke problems of this sort, pick up a copy of my book, Programming Microsoft Internet Explorer 5.0 (Microsoft Press, 1999). This book should be in stores very soon.

Conclusion

    Keystroke problems that occur when hosting the WebBrowser control are one of the hardest problems to resolve. The solution is always to call the TranslateAccelerator method of IOleInPlaceActiveObject. But since every application is different and written with different development libraries, the method used to fix keystroke problems is going to differ. If you follow the steps in this article, you should be able to solve most keystroke problems when hosting the WebBrowser control.

This article is adapted from the upcoming book Programming Microsoft Internet Explorer 5.0 by Scott Roberts (Microsoft Press, Spring 1999).

From the April 1999 issue of Microsoft Internet Developer.