Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to customize the context menus of a WebBrowser control via the IDocHostUIHandler interface.

0.00/5 (No votes)
27 Oct 2003 3  
This article describes how we can customize the contextual menus of the WebBrowser control, by implementing the IDocHostUIHandler.

Important note

Unfortunately, the customization approach, that I have originally used in this article, is not suitable for MFC applications (see the "Important Notice" message thread for more) and you should ignore the sections 4 and 6 of my article. On the contrary, section 5 is still 100% valid and can still be quite useful in the customization of the WebBrowser control context menus. The revised sample projects are using a new, much better customization approach that is going to be comprehensively discussed in the next update of this article, which will hopefully be ready in a couple of weeks. I am publishing this semi-documented and not fully-tested code, because I am having indications that some developers may need to have this code much sooner than the day of my next update. For each revised sample there is also a Readme.htm file that briefly describes how the sample works.

Table of contents

1. Why do we need to customize the context menus of the WebBrowser control (WBC)?

The WebBrowser control (WBC) is a very powerful ActiveX control equipped with many useful capabilities, such as HTML, XML and text data viewing, web browsing, downloading and document viewing (it can display PDF, Word, Excel, PowerPoint and other documents). A description of its capabilities and its usefulness already exists in the MSDN site [1,2,3,4] and is out of the scope of this article. This article is going to deal only with the context menus, which WebBrowser control provides when it displays HTML, XML or text data and we will particularly discuss the customization of these context menus.

When the WebBrowser control displays HTML, XML or text data, it provides by default, a powerful set of context menus that can be used to manipulate its content. Hence, significant control over its content is automatically being granted to the end-user. For instance, the end-user will be able to navigate forward and back whenever he chooses, to change the current encoding, to print/export/reload the content and he can also view the source data. By default the WebBrowser control does not need any assistance or approval from the application to do any of these. In fact, the application might not even know that these actions ever occur! Obviously, the fact that end-user has so much control over the content of our WebBrowser control is not always desirable and can introduce severe difficulties in our application design. If this is the case, then we have to customize the default context menus of the WebBrowser control and accommodate them to the specific needs of our application.

2. The basic customization techniques that can be used.

I am aware of two basic approaches that can be used in order to customize the context menus of the WebBrowser control. We can achieve this customization by overriding the CWinApp::PreTranslateMessage() method, or by implementing the IDocHostUIHandler interface. However, both these techniques are applicable only when the WebBrowser control displays HTML, XML or text data, for reasons that we are going to explain in the next two paragraphs.

2.1. Overriding the CWinApp::PreTranslateMessage().

By overriding the CWinApp::PreTranslateMessage(), our application will handle the right click events of the WebBrowser control window, instead of the WebBrowser control itself. This method has been discussed in another CodeProject article written by Santhosh Cheeran. [22] Using this approach we can achieve the following:

  • Completely suppress all the default context menus.
  • Implement our own custom popup, that will replace all the default context menus.

Limitations

In case we drop a PDF or Word document into the WebBrowser control area, the document will be displayed normally, but our customization code will stop working! The reason for this behavior can be easily found with the help of the Spy++ tool. Spy++ shows clearly that the window, which actually displays these documents, belongs to a separate external process. Hence, the right-click events on this window will never appear in the event queue of our application and our PreTranslateMessage() method will never be called to process them!

Requirements

The WebBrowser control is available only to programs running under Windows NT 4.0 or later, in which Internet Explorer 4.0 or later has been installed.

2.2. Implementing the IDocHostUIHandler interface.

The implementation of the IDocHostUIHandler interface is not a hack or something that just works and the usefulness of this technique goes far beyond the customization of the context menus of WebBrowser control. IDocHostUIHandler together with IDocHostUIHandler2 and IDocHostShowUI are interfaces designed by Microsoft to compose the heart of the WebBrowser control UI customization.[8] However, in this article we only need to discuss how the IDocHostUIHandler interface can be used to customize the WebBrowser control context menus. Using this method we can achieve the following:

  • Completely suppress all the default context menus.
  • Implement our own custom popup that will replace all the default context menus.
  • Suppress some of the default context menus.
  • Implement our own custom popup that will replace only some of the default context menus.
  • Modify/remove some menu items of the default context menus or add some new items to them.

Limitations

When the WebBrowser control displays HTML, XML or text data, it actually hosts the MSHTML active document, [2,5,6,7] which handles the parsing and rendering of the data. On the other hand, WebBrowser control can display many other data types that MSHTML cannot process. (such as PDF, Word or Excel data) In these cases the WebBrowser control has to host other active documents, specialized on these particular types of data. [2] However, the IDocHostUIHandler interface is not designed to customize the WebBrowser control itself, but in fact it can customize only the MSHTML active document and only MSHTML can call its methods. [14] Unfortunately, this means that, when WebBrowser control displays data that must be processed from an active document other than MSHTML, (such as PDF, Word or Excel data) then the IDocHostUIHandler interface is not used and our customization code becomes inactive.

Requirements

The WebBrowser control is available to programs running under Windows NT 4.0 or later, in which Internet Explorer 4.0 or later has been installed, but many of its customization features require that IE 5 or IE 5.5 is present. Moreover, with this customization technique we can not modify the menu items of the default WebBrowser control context menus, unless the Shdoclc.dll system file is present. In general, this technique is expected to behave exactly as described in Windows Me/2000/XP and it is quite possible (although not tested) that also works great on Windows 98 with IE 5 or later.

3. The objective of this article.

In this article we are going to discuss the customization of the WebBrowser control context menus via the IDocHostUIHandler interface. When I was trying to implement this customization technique myself, I was able to find sufficient documentation [8] which is discussing the nature and the usage of the IDocHostUIHandler interface and I also found a small but helpful code example. [10] However, in this article I am providing three important facilities which I wasn't able to find anywhere on the Internet and I believe that the lack of them has cost me much valuable time:

  1. A step by step guide: This article intends to provide a step by step guide that explains in detail the customization of the WebBrowser control context menus via the IDocHostUIHandler interface. The article will also provide all the necessary references for anyone who wants to go deeper.
  2. A VC++ sample project: This article is accompanied by a VC++ project which can build a simple sample application that demonstrates the customization of the WebBrowser control context menus via the IDocHostUIHandler interface. By modifying this sample application, or by investigating it with the debugger, the developer can quickly find answers to specific technical issues that are not in the scope of this technical article.
  3. Some reusable code: Furthermore, some of the code that is provided can be used by the developer as a foundation. Instead of writing and testing by himself all the customization code, the developer can simply modify the fairly tested implementation of the CWebCtrlInterFace interface, that can be found inside the WebCtrlInterFace.cpp and WebCtrlInterFace.h files.

I believe that providing the above facilities, this article can become an important time saver for anyone who tries to customize the WebBrowser control context menus for the first time. In fact this was my primary objective when I wrote it: To help the developer save valuable time.

4. Implementation of the preliminary stuff.

Following the instructions found in MSDN site [9] we'll implement both the IDocHostUIHandler and IOleClientSite interfaces. The additional IOleClientSite interface will be passed to the WebBrowser control, through the IOleObject::SetClientSite() method and it will act afterwards as an intermediate. Namely, the WebBrowser control will use the QueryInterface() method of this IOleClientSite instance to obtain the IDocHostUIHandler instance that we provide. In this section, we are going to discuss the implementation issues that are not directly related to the WebBrowser control context menus customization. The hard core of our customization is the implementation of the IDocHostUIHandler::ShowContextMenu(), which is going to be discussed in the next section.

4.1. The "CWebCtrlInterFace" class declaration.

We write the declaration of our CWebCtrlInterFace C++ class, which will control the WebBrowser control customization and is derived from both the IOleClientSite and IDocHostUIHandler classes. This way we'll have only one object to maintain and our interfaces will share one common implementation of their IUnknown base interface. The rest are rather trivial tasks. We'll just have to read the documentation [12,13,14] and write down the declarations of the CWebCtrlInterFace class methods accordingly. The complete CWebCtrlInterFace declaration can be found inside the WebCtrlInterFace.h header file.

4.2.Implementation of the "IUnknown" interface methods.

We'll implement the three methods of the IUnknown base interface class. Fortunately there is a comprehensive example in the MSDN site, [11] that makes this implementation rather trivial. The IUnknown::AddRef() and IUnknown::Release() methods are just managing the existence of their object and their implementation does not depend on the type of the object. Hence, for these two methods we are using exactly the same implementation that the example introduces.

On the other hand, IUnknown::QueryInterface() has to match our specific needs and must return both our IDocHostUIHandler and IOleClientSite interface instances. However, its implementation is also very simple. We just have to check the riid input parameter and then return the appropriate interface pointer via the ppv output parameter. In fact, all the interface pointers, that our IUnknown::QueryInterface() implementation needs to return, are pointers to ancestor classes of our CWebCtrlInterFace class. Thus, we can easily obtain them by simply up-casting this pointer accordingly. The complete IUnknown implementation can be found inside the WebCtrlInterFace.cpp source file and in the Listing#1 as well.

4.3. Implementing the "neutral" behavior in the IOleClientSite and IDocHostUIHandler methods.

Since we only need to use IOleClientSite interface as an intermediate, we have to implement it in such a way that its existence will not affect the behavior of the WebBrowser control. Furthermore, except from the ShowContextMenu(), which is affecting the behavior of the WebBrowser control context menus, all the other methods of our IDocHostUIHandler interface must act like the didn't exist. Hence we need to implement a "neutral" behavior in most of our CWebCtrlInterFace methods and we are achieving this by using the following techniques:

  1. The CWebCtrlInterFace class is equipped with the SetDefaultClientSite() public method, which receives the default the IOleClientSite interface instance and is using it afterwards to obtain the default IDocHostUIHandler instance. These two instances are stored inside the CWebCtrlInterFace class and will be used to delegate any interface call that we don't want to customize. The Listing#2 demonstrates the implementation of the SetDefaultClientSite() method.
  2. In case that the default interfaces have not been provided to our CWebCtrlInterFace instance, the "neutral" behavior will be achieved by returning the appropriate return codes in the interface methods. The exact return code that will be used in each method is determined after reading the relevant documentation [13,14] and is usually E_NOTIMP, S_FALSE or E_FAIL. Moreover, we often have the obligation to assure that the output param pointers are set to NULL, in the interface methods that do not intend to alter the default behavior of the WebBrowser control .

The complete IOleClientSite and IDocHostUIHandler implementations can be found inside the WebCtrlInterFace.cpp source file.

5. Implementation of the "IDocHostUIHandler::ShowContextMenu()" method.

When IDocHostUIHandler interface is properly implemented and installed, the WebBrowser control (the MSHTML actually) calls its ShowContextMenu() interface method, instead of displaying itself the context menus. Therefore, the implementation of this interface method constitutes the core of the WebBrowser control context menu customization. In the MSDN site we can find documentation that explains the syntax of the ShowContextMenu() interface method and the meaning of its parameters [15]. Furthermore, there is an MSDN article [10] that is discussing, among other things, the implementation of the ShowContextMenu() interface method and also provides a helpful code example. In the following paragraphs I am trying to provide a much more comprehensive look at the implementation of this interface method, while I am frequently referring to the MSDN site documentation in order to avoid unnecessary repetition.

5.1. Vital technical information about the context menus of the WebBrowser control.

First of all I should clarify, that the context menus we want to customize, are in fact handled by the active document that the WebBrowser control is hosting and not by the WebBrowser control itself. As we mentioned before, [p2.2] we will only deal with the case that WebBrowser control is hosting the MSHTML active document, which can handle HTML, XML or text data. The active documents that can handle PDF, Word, or Excel files, are providing their own, completely different, context menus and they don't recognize or use our IDocHostUIHandler interface.

MSHTML provides context menus specialized for images, tables, text selections and also a default context menu. Most of the times, when customizing the WebBrowser control context menus we don't really want to give up all these great facilities; we just want to make some specific modifications. Hence, one of the most important technical advantages of the IDocHostUIHandler interface over alternative customization techniques, is that it lets us choose which menus to customize and which to leave intact. The first parameter of the ShowContextMenu() interface method represents the shortcut menu value of the menu that is going to be displayed by the MSHTML in case IDocHostUIHandler doesn't interfere. Moreover, in the mshtmhst.h header file we can find constants that correspond to each menu value (such as CONTEXT_MENU_DEFAULT, CONTEXT_MENU_TEXTSELECT, etc) and can be compared to the first parameter of the ShowContextMenu() method. This way, it is quite easy to selectively customize the WebBrowser control context menus, like we are going to demonstrate later in this article. [p5.4]On the other hand, if we choose to customize the WebBrowser control shortcut menus by overriding the PreTranslateMessage() method of the CWinApp class, [22] we'll never know which context menu is being replaced each time we display our custom menu.

Sometimes, it seems appropriate to modify a particular MSHTML context menu (remove, add, replace or modify its menu items) instead of replacing it with a custom menu. There are also situations in which we need to investigate the context menu items at runtime. In these cases we can take advantage of the fact that all MSHTML context menus are stored as sub-menus in the #24641 menu resource of the Shdoclc.dll system file. [10] Furthermore, the shortcut menu values of the mshtmhst.h header file represent the zero-based relative position of the corresponding shortcut menus into this menu resource. Hence, it's a rather trivial task to load the menu resource, retrieve the handle of the particular shortcut menu and then manipulate it accordingly. Later in this article [p5.6] we'll demonstrate how all these can be done by using C++ and some Win32 SDK calls.

In case we want to manipulate a particular item of a MSHTML shortcut menu, it is handy to use the MSHTML Command IDs, (such as IDM_VIEWSOURCE, IDM_SELECTALL, etc) which are defined into the mshtmcid.h header file and refer to the menu items by their Command ID. Many Win32 SDK calls that are used to manipulate menu items can accept the Command ID as a parameter (usually when the MF_BYCOMMAND flag is used). These Command IDs are also vital in case we are providing our own custom menu that borrows items from the MSHTML shortcut menus. We can bind these items to the appropriate Command IDs and when the menu selection is done we just have to send a WM_COMMAND message passing the corresponding Command ID as first message parameter (wParam). Later in this article [p.5.5, p.5.6] we'll demonstrate how all these can be done.

Finally, there is one important mistake in the documentation that I have to point out, before we proceed towards the implementation of the ShowContextMenu() interface method. The dwID parameter of the ShowContextMenu() does not represent a bitwise shift of the value 0x1 by the shortcut menu values, like the documentation [15] mentions. In fact dwID represents the shortcut menu values themselves.

5.2. Introducing the CWebCtrlInterFace customization modes.

The ShowContextMenu() method of the IDocHostUIHandler interface can be implemented in many different ways, achieving different types of customization respectively. [p.2.2] In order to improve the usefulness and the reusability of the CWebCtrlInterFace class, [p.3, 3] I have implemented in its ShowContextMenu() method, a variety of "customization modes". Hence, the CWebCtrlInterFace class contains the m_contextMenuMode field, which holds the current context menu customization mode and determines how the ShowContextMenu() should work each time. The current customization mode can be controlled at run time through GetContextMenuMode() and SetContextMenuMode() methods of the CWebCtrlInterFace class. Each one of the customization modes virtually demonstrates an individual example of how we can customize the MSHTML shortcut menus and can have one of the following values:

kDefaultMenuSupport MSHTML displays its context menus as usual.
kNoContextMenu All the MSHTML context menus are suppressed.
kTextSelectionOnly All but the text selection MSHTML context menu are suppressed.
kAllowAllButViewSource The "View Source" menu item of the default MSHTML context menu is removed.
kCustomMenuSupport Our own custom popup is replacing the default MSHTML context menu.

5.3. Implementation of the "kDefaultMenuSupport" and "kNoContextMenu" modes.

The kDefaultMenuSupport and kNoContextMenu modes are the easiest to implement. By returning S_OK in the ShowContextMenu() interface method, the application denotes that the request for a context menu has been already handled successfully by the IDocHostUIHandler interface instance and there is nothing more that MSHTML should do about it. Thus, the kNoContextMenu mode is implemented by returning S_OK, while the kDefaultMenuSupport mode is implemented by falling back to the default MSHTML behavior (see [p4.3] for details). The implementation of the ShowContextMenu() interface method can be found in the Listing#3 and in the WebCtrlInterFace.cpp source file as well.

5.4. Implementation of the kTextSelectionOnly mode.

The implementation of the kTextSelectionOnly mode can be considered as a combination of the kDefaultMenuSupport and kNoContextMenu implementations. If the dwID input parameter of the ShowContextMenu() interface method is equal to the CONTEXT_MENU_TEXTSELECT constant, then code falls back to the default MSHTML behavior (see [p4.3] for details) and the MSHTML text selection context menu is displayed. Otherwise the return value is set to S_OK and no context menu is displayed. The code that implements the ShowContextMenu() interface method can be found in the Listing#3 and in the WebCtrlInterFace.cpp source file as well.

5.5. Implementation of the kCustomMenuSupport mode.

In this customization mode the default MSHTML shortcut menu is replaced by a custom one, leaving all other shortcut menus intact. Namely, if the dwID input parameter of the ShowContextMenu() interface method is equal to the CONTEXT_MENU_DEFAULT constant, the CustomContextMenu() function is called and our custom menu is displayed. Otherwise code falls back to the default MSHTML behavior (see [p4.3] for details).

The CustomContextMenu() function loads the custom menu and then uses the TrackPopupMenu() [17] Win32 SDK call to display it, in the same manner that we traditionally display the shortcut menus. [16] My custom menu intentionally borrows items from the MSHTML shortcut menus and the values of the menu item identifiers are equal to the corresponding MSHTML command IDs, which are defined in the mshtmcid.h header file. TrackPopupMenu() function is called with the TPM_RETURNCMD flag set, in order to return the command ID of the selected menu item, when the menu tracking is done. Then a WM_COMMAND message is send, passing this command ID as the first message parameter (wParam), and the command is executed.

The TrackPopupMenu() function takes as parameters the handle of the owner window and the position of the shortcut menu in screen coordinates. The window handle is also needed in order to send the WM_COMMAND message that executes the selected command. Hence, the CustomContextMenu() function must take as input parameters the ppt and pcmdtReserved input parameters of the ShowContextMenu() interface method. [15] The ppt parameter provides the screen coordinates for the menu, while the pcmdtReserved parameter can be used to obtain the IOleWindow interface pointer that can provide the desired window handle through its GetWindow() interface method. The code that implements the CustomContextMenu() function can be found in the Listing#4 and in the WebCtrlInterFace.cpp source file as well.

5.6. Implementation of the kAllowAllButViewSource mode.

The kAllowAllButViewSource customization mode is quite similar to the kCustomMenuSupport customization mode. The main difference is that instead of displaying a custom popup I modify the default MSHTML shortcut menu and I display it afterwards like any other shortcut menu. "#A8">[16] Again, if the dwID input parameter of ShowContextMenu() is equal to the CONTEXT_MENU_DEFAULT constant the ModifyContextMenu() function is called, otherwise code fallbacks to the default MSHTML behavior (see [p4.3] for details).

ModifyContextMenu() function is derived from the IDocHostUIHandler::ShowContextMenu() interface method of the "WebBrowser Customization" MSDN article [10] and has only minor differences from the original code. Its task is to remove the "view source" menu item from the default MSHTML shortcut menu and then display the menu appropriately. Apart from some code that handles the language submenu and the shortcut menu extensions (which are not in the scope of this article), ModifyContextMenu() virtually implements what we have already discussed in the [p5.1].

ModifyContextMenu() takes as input parameters the dwID, ppt and pcmdtReserved input parameters of the ShowContextMenu() interface method. [15] The pcmdtReserved input parameter is used to get the IOleWindow interface pointer and then obtain from it a handle to the owner window of the shortcut menu. The dwID parameter, on the other hand, represents the zero-based relative position of the MSHTML shortcut menus into the #24641 menu resource of the Shdoclc.dll system file. Hence the value of dwID is used to obtain the menu handle of the default MSHTML shortcut menu through the GetSubMenu() [18] Win32 SDK function. Afterwards, the menu handle is passed together with the IDM_VIEWSOURCE Command ID in the DeleteMenu() [19] Win32 SDK function, which removes the "view source" menu item of the shortcut menu. The menu handle, the handle of the owner window and the screen coordinates of the ppt input parameter are all passed to the TrackPopupMenu() [17] Win32 SDK function, which displays the shortcut menu.

The last task of ModifyContextMenu() is to make sure that WebBrowser control will respond correctly to the shortcut menu selection, by sending the appropriate WM_COMMAND message to the owner window, when the menu tracking is done. Namely, TrackPopupMenu() is called with the TPM_RETURNCMD flag set, in order to return the command ID of the selected menu item. This command ID will be used afterwards as the first message parameter (wParam) of the WM_COMMAND message that ModifyContextMenu() sends if menu tracking ends successfully.

One final detail is that in the implementation of the ModifyContextMenu() function Listing#5 I am also deleting a mysterious IDM_EXTRA_ITEM menu item. This is just a quick (and dirty?) way to eliminate an extra menu separator that appears when deleting the "view source" menu item. I am not feeling proud about this particular hack and I am looking for something better, but I had no luck till now (Any ideas?). The code that implements the ModifyContextMenu() function can be found in the Listing#5 and in the WebCtrlInterFace.cpp source file as well.

6. The sample application.

This article is also accompanied by a ready-to-build VC++ sample project. The sample project builds a simple application that demonstrates the customization of the WebBrowser control context menus via the IDocHostUIHandler interface. In the sample application you can change the context menu behavior at run time, by choosing between different types of customization from the "mode" menu. (See screenshot at the top of the article.) And, of course, you can also modify that application, or investigate it with the debugger, in order to find answers to technical issues that have not been discussed in this article.

In this sample application I prefer to use the CHtmlView MFC class, "#A8">[21] instead of hosting the WebBrowser control directly. CHtmlView provides the functionality of the WebBrowser control within the context of MFC's document-view architecture and is often more convenient to use than the WebBrowser control itself. Bellow I am providing a step-by-step description of how this sample application has been created and how the CWebCtrlInterFace instance should be configured and installed:

  • Step 1: Creating a typical Web Browser-Style Application.

    The MFC Application Wizard is used to create a typical "Web Browser-Style MFC Application". [20] For simplicity it is preferable to create an SDI application.

  • Step 2: Creating and releasing the CWebCtrlInterFace instance.

    The constructor of the CWebBrowserView class creates an CWebCtrlInterFace instance which is stored afterwards in the m_iClientSite member variable. This m_iClientSite instance is released when the CWebBrowserView window receives the WM_NCDESTROY message. The constructor also tests for the existence of the Shdoclc.dll system file and updates the m_SHDOCLC_DLL_Found member variable accordingly. Listing#6

  • Step 3: Installing the new IOleClientSite and IDocHostUIHandler implement interfaces.

    The OnInitialUpdate() method of the CWebBrowserView class obtains the current IOleClientSite interface of the WebBrowser control and makes it the default IOleClientSite interface of the m_iClientSite instance. [p.4.3] Then OnInitialUpdate() installs the new interfaces by making the m_iClientSite instance current IOleClientSite interface [p.4] of the WebBrowser control. Listing#7

  • Step 4: Creating the "mode" menu.

    The "mode" menu is added to the application menus with the menu editor. The items of the "mode" menu correspond to the CWebCtrlInterFace customization modes [p.5.2] and can be viewed in the screenshot at the top of the article. The corresponding command handlers, for both the ON_COMMAND and the ON_UPDATE_COMMAND_UI handler types, are also added to the CWebBrowserView class. (Using the old ClassWizard or the newer Event Handler Wizard.)

  • Step 5: Implement the "mode" menu behavior.

    The command handlers that control the behavior of the "mode" menu can be implemented very easily. The implementation of the ON_COMMAND handlers simply calls the SetContextMenuMode() method of the m_iClientSite instance to set the appropriate CWebCtrlInterFace customization mode. On the other hand, the implementation of the ON_UPDATE_COMMAND_UI handlers gets the current customization mode through the GetContextMenuMode() method of the CWebCtrlInterFace class and then marks the appropriate menu item as checked. An example of this implementation is demonstrated in the Listing#8.

7. Code listings.

7.1. Listing #1.

HRESULT STDMETHODCALLTYPE CWebCtrlInterFace::QueryInterface(REFIID riid, 
                                                              LPVOID *ppv) 
{
HRESULT result = S_OK;

    // Always set out parameter to NULL, validating it first 

    if (IsBadWritePtr(ppv, sizeof(LPVOID))) 
        result = E_INVALIDARG;  

    if (result == S_OK)
    {
        *ppv = NULL; 
 
        if ( IsEqualIID( riid, IID_IUnknown ) )
            *ppv = this;
        else if ( IsEqualIID( riid, IID_IOleClientSite ) )
            *ppv = (IOleClientSite *) this;
        else if ( IsEqualIID( riid, IID_IDocHostUIHandler ) )
            *ppv = (IDocHostUIHandler *) this;
        else
            result = E_NOINTERFACE;
    }

    if (result == S_OK)
        this->AddRef(); 
 
    return result; 
}

ULONG STDMETHODCALLTYPE CWebCtrlInterFace::AddRef() 
{    
    InterlockedIncrement(&m_cRef); 
    return m_cRef; 
} 
 
ULONG STDMETHODCALLTYPE CWebCtrlInterFace::Release() 
{ 
    // Decrement the object's internal counter 

    ULONG ulRefCount = InterlockedDecrement(&m_cRef); 
 
    if (0 == m_cRef) 
    {
        delete this; 
    }
 
    return ulRefCount; 
}

7.2. Listing #2.

VOID CWebCtrlInterFace::SetDefaultClientSite(IOleClientSite *pClientSite)
{
    if (pClientSite != NULL)
    {
        pClientSite->AddRef();

        m_defaultClientSite = pClientSite;
        m_defaultClientSite->QueryInterface(IID_IDocHostUIHandler, 
                                       (VOID **)&m_defaultDocHostUIHandler);
    }
    else
    {
        if (m_defaultClientSite != NULL)
        {
            m_defaultClientSite->Release();
            m_defaultClientSite = NULL;
        }

        if (m_defaultDocHostUIHandler != NULL)
        {
            m_defaultDocHostUIHandler->Release();
            m_defaultDocHostUIHandler = NULL;
        }
    }
}

7.3. Listing #3.

HRESULT STDMETHODCALLTYPE CWebCtrlInterFace::ShowContextMenu(DWORD dwID, 
             POINT *ppt, IUnknown *pcmdtReserved, IDispatch *pdispReserved)
{
HRESULT result    = S_FALSE; //Dont Interfere

BOOL    handled   = FALSE;

    switch ( m_contextMenuMode )
    {
        case kDefaultMenuSupport:
            break;

        case kNoContextMenu:
            result    = S_OK;
            handled   = TRUE;
            break;

        case kTextSelectionOnly:
            if (dwID != CONTEXT_MENU_TEXTSELECT)
            {
                result    = S_OK;
                handled   = TRUE;
            }
            break;

        case kAllowAllButViewSource:
            if (dwID == CONTEXT_MENU_DEFAULT)
            {
                result    = ModifyContextMenu(dwID, ppt, pcmdtReserved);
                handled   = TRUE;
            }
            break;

        case kCustomMenuSupport:
            if (dwID == CONTEXT_MENU_DEFAULT)
            {
                result    = CustomContextMenu(ppt, pcmdtReserved);
                handled   = TRUE;
            }
            break;
    }

    if (! handled)
    {
        if (m_defaultDocHostUIHandler != NULL)
            result = m_defaultDocHostUIHandler->ShowContextMenu(dwID, ppt, 
                    pcmdtReserved, pdispReserved);
        else
            result = S_FALSE;
    }

    return result;
}

7.4. Listing #4.

HRESULT CustomContextMenu(POINT *ppt, IUnknown *pcmdtReserved) 
{
    IOleWindow *oleWnd      = NULL;
    HWND       hwnd         = NULL;
    HMENU      hMainMenu    = NULL;
    HMENU      hPopupMenu   = NULL;
    HRESULT    hr           = 0;
    INT        iSelection   = 0;

    if ((ppt == NULL) || (pcmdtReserved == NULL))
        goto error;

    hr = pcmdtReserved->QueryInterface(IID_IOleWindow, (void**)&oleWnd);
    if ( (hr != S_OK) || (oleWnd == NULL))
        goto error;

    hr = oleWnd->GetWindow(&hwnd);
    if ( (hr != S_OK) || (hwnd == NULL))
        goto error;

    hMainMenu = LoadMenu(AfxGetInstanceHandle(),
                     MAKEINTRESOURCE(IDR_CUSTOM_POPUP));
    if (hMainMenu == NULL)
        goto error;

    hPopupMenu = GetSubMenu(hMainMenu, 0);
    if (hPopupMenu == NULL)
        goto error;

    // Show shortcut menu

    iSelection = ::TrackPopupMenu(hPopupMenu,
                        TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,
                        ppt->x,
                        ppt->y,
                        0,
                        hwnd,
                        (RECT*)NULL);

    // Send selected shortcut menu item command to shell

    if (iSelection != 0)
        (void) ::SendMessage(hwnd, WM_COMMAND, iSelection, NULL);

error:

    if (hMainMenu != NULL)
        ::DestroyMenu(hMainMenu);

    return S_OK;
}

7.5. Listing #5.

HRESULT ModifyContextMenu(DWORD dwID, POINT *ppt, IUnknown *pcmdtReserved) 
{
    //#define IDR_BROWSE_CONTEXT_MENU  24641

    //#define IDR_FORM_CONTEXT_MENU    24640

    #define SHDVID_GETMIMECSETMENU   27
    #define SHDVID_ADDMENUEXTENSIONS 53

    HRESULT hr;
    HINSTANCE hinstSHDOCLC;
    HWND hwnd;
    HMENU hMenu;
    IOleCommandTarget *spCT;
    IOleWindow *spWnd;
    MENUITEMINFO mii = {0};
    VARIANT var, var1, var2;

    hr = pcmdtReserved->QueryInterface(IID_IOleCommandTarget, 
                                                (void**)&spCT);
    hr = pcmdtReserved->QueryInterface(IID_IOleWindow, (void**)&spWnd);
    hr = spWnd->GetWindow(&hwnd);

    hinstSHDOCLC = LoadLibrary(TEXT("SHDOCLC.DLL"));

    hMenu = LoadMenu(hinstSHDOCLC,
                     MAKEINTRESOURCE(IDR_BROWSE_CONTEXT_MENU));

    hMenu = GetSubMenu(hMenu, dwID);

    // Get the language submenu

    hr = spCT->Exec(&CGID_ShellDocView, 
           SHDVID_GETMIMECSETMENU, 0, NULL, &var);

    mii.cbSize = sizeof(mii);
    mii.fMask  = MIIM_SUBMENU;
    mii.hSubMenu = (HMENU) var.byref;

    // Add language submenu to Encoding context item

    SetMenuItemInfo(hMenu, IDM_LANGUAGE, FALSE, &mii);

    // Insert Shortcut Menu Extensions from registry

    V_VT(&var1) = VT_PTR;
    V_BYREF(&var1) = hMenu;

    V_VT(&var2) = VT_I4;
    V_I4(&var2) = dwID;

    hr = spCT->Exec(&CGID_ShellDocView, 
           SHDVID_ADDMENUEXTENSIONS, 0, &var1, &var2);

    // Remove View Source

    ::DeleteMenu(hMenu, IDM_VIEWSOURCE, MF_BYCOMMAND);
    // Remove the item that produces the exta separator

    ::DeleteMenu(hMenu, IDM_EXTRA_ITEM, MF_BYCOMMAND);

    // Show shortcut menu

    int iSelection = ::TrackPopupMenu(hMenu,
                          TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,
                          ppt->x,
                          ppt->y,
                          0,
                          hwnd,
                          (RECT*)NULL);

    // Send selected shortcut menu item command to shell

    if (iSelection != 0)
        LRESULT lr = ::SendMessage(hwnd, WM_COMMAND, iSelection, NULL);

    FreeLibrary(hinstSHDOCLC);
    return S_OK;
}

7.6. Listing #6.

CWebBrowserView::CWebBrowserView()
{
    m_iClientSite = new CWebCtrlInterFace;
    
    if (m_iClientSite != NULL)
        m_iClientSite->AddRef();

    {
        HINSTANCE tstInstance = LoadLibrary(TEXT("SHDOCLC.DLL"));

        m_SHDOCLC_DLL_Found = (tstInstance != NULL);

        if (! m_SHDOCLC_DLL_Found)
        {
            ::AfxMessageBox(_T("Cannot Load Library SHDOCLC.DLL.\n"
                "The \"No View Source Choice\" mode will not work."));
        }

        FreeLibrary(tstInstance);
    }
}

void CWebBrowserView::OnNcDestroy()
{
    if (m_iClientSite != NULL)
    {
        m_iClientSite->Release();
        m_iClientSite = NULL;
    }

    CHtmlView::OnNcDestroy();
}

7.7. Listing #7.

void CWebBrowserView::OnInitialUpdate()
{
IOleObject *pIOleObj = NULL;

    CHtmlView::OnInitialUpdate();

    if (m_iClientSite != NULL)
    {
        if (m_pBrowserApp != NULL)
            m_pBrowserApp->QueryInterface(IID_IOleObject, 
                                      (void**)&pIOleObj);

        if (pIOleObj != NULL)
        {
        IOleClientSite *oldClientSite = NULL;

            if (pIOleObj->GetClientSite(&oldClientSite) == S_OK)
            {
                m_iClientSite->SetDefaultClientSite(oldClientSite);
                oldClientSite->Release();
            }

            pIOleObj->SetClientSite(m_iClientSite);
        }
    }

    Navigate2(_T("http://www.codeproject.com/") ,NULL,NULL);
}

7.8. Listing #8.

void CWebBrowserView::On_CMM_NoContextMenu() 
{
    if (m_iClientSite != NULL)
        m_iClientSite->SetContextMenuMode(kNoContextMenu);
}

void CWebBrowserView::OnUpdate_CMM_NoContextMenu(CCmdUI* pCmdUI) 
{
    if (pCmdUI != NULL)
    {
        if (m_iClientSite != NULL)
        {
            pCmdUI->Enable(TRUE);
            pCmdUI->SetCheck(m_iClientSite->GetContextMenuMode() 
                                                      == kNoContextMenu);
        }
        else
        {
            pCmdUI->Enable(FALSE);
            pCmdUI->SetCheck(FALSE);
        }
    }
}

8. References.

  1. WebBrowser Control Overviews and Tutorials (MSDN)
  2. Reusing the WebBrowser Control (MSDN)
  3. WebBrowser Object (MSDN)
  4. WebBrowser Customization (MSDN)
  5. About MSHTML (MSDN)
  6. Reusing MSHTML (MSDN)
  7. MSHTML Reference (MSDN)
  8. WebBrowser Customization, paragraph: WebBrowser Customization Architecture (MSDN)
  9. WebBrowser Customization, paragraph: How It Works (MSDN)
  10. WebBrowser Customization, paragraph: IDocHostUIHandler::ShowContextMenu (MSDN)
  11. Implementing IUnknown in C++ (MSDN)
  12. IUnknown Interface (MSDN)
  13. IOleClientSite Interface (MSDN)
  14. IDocHostUIHandler Interface (MSDN)
  15. IDocHostUIHandler::ShowContextMenu (MSDN)
  16. Using Menus, paragraph: Displaying a Shortcut Menu (MSDN)
  17. TrackPopupMenu Function (MSDN)
  18. GetSubMenu Function (MSDN)
  19. DeleteMenu Function (MSDN)
  20. Creating a Web Browser-Style MFC Application (MSDN)
  21. CHtmlView Class (MSDN)
  22. How to Modify-Remove the context menu shown by an IE WebBrowser Control (Code Project)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here