Click here to Skip to main content
15,884,472 members
Articles / Desktop Programming / MFC

Solving the Windows Webbrowser Control Focus-stealing Bug

Rate me:
Please Sign up or sign in to vote.
4.80/5 (9 votes)
20 Mar 2018CPOL8 min read 12.6K   173   7   1
A fully working fix for this longstanding problem, tested in Windows 7 - 10

Introduction

If you're an MFC programmer, you almost certainly will want to use the Webbrowser ActiveX control for displaying HTML in your application. It wraps up all the functionality of Internet Explorer and, because that is already present in all installations of Windows, even Windows 10 thankfully, it doesn't require any other libraries or browsers to be installed.

For use in a multi-document interface (MDI) application, though, it has a fatal flaw - it keeps stealing the focus from other windows. Sadly, this does really make it literally impossible to use in many contexts - nothing else works as it should. If we can prevent this, we have a useful and extensible control - if not, it's a tricky choice of unpalatable alternatives.

Background

I have been maintaining a large C++ MFC pre .NET program with a lot of users as my day job for well over a decade. While this is all 'legacy' code, it works well and rewriting it substantially isn't a commercially-viable proposition. I think there must be a lot of people in similar circumstances: if this solution had been available anywhere on the web, it would have saved me 3 days' work, and if it helps someone else out in the same situation, then that will be great.

I will only essentially present my solution here as that is almost certainly why you're reading this: I have learned a bit about some of the more obscure aspects of Windows while figuring it out but this is essentially a bug fix - the control shouldn't behave as it does and we need to sort that out.

But there is a catch. A lot of the documentation relevant to this issue is very old (most searches that provide any relevant information find articles from 2003 or earlier), and it seems that recently MS have retired a lot of their old code samples, etc. which might have helped. Can we find enough clues anywhere to solve this problem?

Incidentally, I'm using MFC on Visual C++ 2010, but the solution works and has been tested on Windows 7 and Windows 10 and should be fine from VC6 onwards, so read on...

The Solution

An Active X control has an interface to the program hosting it, represented in MFC by a class called COleControlSite. This in turn contains an implementation of the IUnknown interface, which is a general-purpose interface which can be queried using a mechanism defined in the API to find out what services it supports. In this context, 'services' mean anything the control can do: in the case of a web browser it is such things as resizing the window, zooming, browsing to a web address, previous page, etc. Because the services are identified by GUIDs, the system is almost infinitely extensible so any type of control can be added which provides any number of services.

From searching high and low on the web, I managed to piece together the information that the solution to the focus problem lay in this mechanism. There is an interface, IProtectFocus, derived from IUnknown, that we need to support. This gives us a toe in the door, at least.

And since MFC7, the Webbrowser control wrappers (MFC's CHTMLDialog and CHtmlView classes) have a virtual function, CreateControlSite, which allows a derived class to override the default control site and replace it with one that we define ourselves.

Next Steps

As we know, MFC attempts to simplify Windows programming by wrapping Win32 widgets in C++ classes, which aims to make it easier to use them to construct useful programs. However, it's been well said that programming using MFC is mainly an exercise in getting the framework to do things it wasn't designed to do. The thing that makes this possible, however, and the only reason we can use this stuff at all to do more than make toy systems, is that we have access to the source code of the MFC classes.

Here, this access is helpful because we just have to type "IProtectFocus" in one of our source files and hit 'Go to definition' in the context menu to find out what it consists of. Between this and the very sketchy MSDN documentation, we discover that it is (like all such 'interfaces' in C++) an abstract class with only one pure virtual method of its own, AllowFocusChange, which takes a pointer to BOOL (MFC-speak for int used as a boolean) - the function just has to set the thing this points to as FALSE, and magically our widget won't steal the focus.

So far so good, but unfortunately it's not quite that simple. We have to let the hosting program know that the Webbrowser supports the IProtectFocus subclass of IUnknown, and it turns out that this requires another interface to be implemented. This time it's IServiceProvider: this has a member function QueryService which does what we want.

Just when it seems we're getting somewhere, though, we discover that IServiceProvider responds to queries by returning a pointer to the requested interface rather than responding directly to the request with the BOOL we want to communicate. But what exactly is this pointer we have to return? Very little information is available on this subject; it's a pointer to the interface, but what precisely does this mean? Having got so far, though, we're not going to give up just yet.

Implementing Multiple Interfaces on One IUnknown Instance

I was unable to find an example of this anywhere on the web. The only help we have is in a Microsoft Technical Note TN038: MFC/OLE IUnknown Implementation. Hopefully, that still exists when you're reading this but even if not, the solution still follows.

A multipart interface is constructed with the use of macros which define the addresses of the various parts of the interface within the whole. These macros create inner classes which in turn inherit from the specific interface you are implementing. They also provide a lot of boilerplate code which supports the IUnknown mechanism, but make the code look 'wrong' because they declare a lot of things which aren't visible to a reader - you actually have to look carefully at how the macros are defined to have any idea of how the code works. This is a good example of how MFC 'helps'; unless you are going to dig beneath the surface, you have to just follow the rules.

So to cut a long story short, I did finally piece all this together but then the cruel blow. It did actually solve the focus problem, but then crashed when the view hosting the Webbrowser was closed. This crash was deep in the heart of the framework but I had always been suspicious that something of the kind might happen; the IProtectFocus interface returns a pointer to the heap which leaves a potentially fatal situation when it is freed. The framework seems to rely on a method of reference counting to attempt to avoid this but either I didn't implement it correctly or it didn't work as intended.

The last-ditch attempt to fix this fortunately worked: instead of returning a pointer to the interface on the heap, I made a static stub interface and return a pointer to that instead. This pointer is obviously always valid, and although unconventional, the idea seems to work. Job done!

Using the Code

Most of the code comes from MFC's CHtmlView (viewhtml.cpp in the MFC directory). This file contains both the declaration and definition of the default interface, but for non-inline use, it needs to be split into a .h and .cpp file.

I recommend using these exactly as presented: I spent a happy couple of hours trying to find out why a previous version couldn't link and it turned out to be a name clash with a library class I didn't even know existed, so I suggest you leave the names as they are.

Important: My derived class in my code is called CHtmlCtrl, so all references to CHtmlCtrl in the files need to be changed to your derived class's name. In this example, I have used CHtmlDerived.

Add HtmlControlSiteEx.cpp and HtmlControlSiteEx.h to your project.

Then, in your class derived from CHtmlView, override the virtual CreateControlSite function:

Example .h file:

C++
#pragma once

//CHtmlDerived - subclass of CHtmlView fixing the focus-stealing bug

class CHtmlDerived : public class CHtmlView
{
public:
    CHtmlDerived(/*params*/);
    //...
    //declarations
    //...
    virtual BOOL CreateControlSite(COleControlContainer* pContainer,
       COleControlSite** ppSite, UINT nID, REFCLSID clsid);
    //...
    //declarations
    //...

    //ALSO ADD THE FOLLOWING which declare functions defined in HtmlControlSiteEx.cpp
    //...simply copied from CHtmlView's class declaration
        // DocHostUIHandler overrideables
    virtual HRESULT OnShowContextMenu(DWORD dwID, LPPOINT ppt,
        LPUNKNOWN pcmdtReserved, LPDISPATCH pdispReserved);
    virtual HRESULT OnGetExternal(LPDISPATCH *lppDispatch);
    virtual HRESULT OnGetHostInfo(DOCHOSTUIINFO *pInfo);
    virtual HRESULT OnShowUI(DWORD dwID,
        LPOLEINPLACEACTIVEOBJECT pActiveObject,
        LPOLECOMMANDTARGET pCommandTarget, LPOLEINPLACEFRAME pFrame,
        LPOLEINPLACEUIWINDOW pDoc);
    virtual HRESULT OnHideUI();
    virtual HRESULT OnUpdateUI();
    virtual HRESULT OnEnableModeless(BOOL fEnable);
    virtual HRESULT OnDocWindowActivate(BOOL fActivate);
    virtual HRESULT OnFrameWindowActivate(BOOL fActivate);
    virtual HRESULT OnResizeBorder(LPCRECT prcBorder,
        LPOLEINPLACEUIWINDOW pUIWindow, BOOL fFrameWindow);
    virtual HRESULT OnTranslateAccelerator(LPMSG lpMsg,
        const GUID* pguidCmdGroup, DWORD nCmdID);
    virtual HRESULT OnGetOptionKeyPath(LPOLESTR* pchKey, DWORD dwReserved);
    virtual HRESULT OnFilterDataObject(LPDATAOBJECT pDataObject,
        LPDATAOBJECT* ppDataObject);
    virtual HRESULT OnTranslateUrl(DWORD dwTranslate,
        OLECHAR* pchURLIn, OLECHAR** ppchURLOut);
    virtual HRESULT OnGetDropTarget(LPDROPTARGET pDropTarget,
        LPDROPTARGET* ppDropTarget);
};

Example .cpp file:

C++
//CHtmlDerived - subclass of CHtmlView fixing the focus-stealing bug

#include "stdafx.h"
#include "htmlctrl.h"
#include "htmlcontrolsite.h"
//etc

CHtmlDerived::CHtmlDerived(/*params*/) : CHtmlView()
{
    //do stuff
}

//...
//definitions
//...

BOOL CHtmlDerived::CreateControlSite(COleControlContainer* pContainer, 
    COleControlSite** ppSite, UINT nID, REFCLSID clsid)
{
    //uncomment below to use default site:
    //return CHtmlView::CreateControlSite(pContainer, ppSite, nID, clsid);

    ASSERT(ppSite != NULL);
    *ppSite = new CHtmlControlSiteEx(pContainer);
    return TRUE;
}

Build and enjoy!

Conclusion

There are quite a lot of helpful articles already on CodeProject which deal with aspects of the Webbrowser control but nothing I found anywhere online seems to give precise instructions on how to fix this particular issue. It's relatively simple to implement the fix described here, and once you get going with this control (hint: have a play with PreTranslateMessage), you discover all sorts of other useful things you can do.

Most of the other material on the web which would help with this has largely become a sea of broken links. Maybe I'm wrong and hardly anyone else still uses old-school MFC, but in my opinion there's still life in it for legacy applications (although if you're doing something new, there are MANY better alternatives), and it's a shame Microsoft seems to be intent on removing old code samples, etc. which are still useful to us.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralInteresting Pin
R.D.H.21-Mar-18 12:27
R.D.H.21-Mar-18 12:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.