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:
- 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.
- 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.
- 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:
- 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.
- 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;
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()
{
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;
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;
iSelection = ::TrackPopupMenu(hPopupMenu,
TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,
ppt->x,
ppt->y,
0,
hwnd,
(RECT*)NULL);
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 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);
hr = spCT->Exec(&CGID_ShellDocView,
SHDVID_GETMIMECSETMENU, 0, NULL, &var);
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_SUBMENU;
mii.hSubMenu = (HMENU) var.byref;
SetMenuItemInfo(hMenu, IDM_LANGUAGE, FALSE, &mii);
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);
::DeleteMenu(hMenu, IDM_VIEWSOURCE, MF_BYCOMMAND);
::DeleteMenu(hMenu, IDM_EXTRA_ITEM, MF_BYCOMMAND);
int iSelection = ::TrackPopupMenu(hMenu,
TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,
ppt->x,
ppt->y,
0,
hwnd,
(RECT*)NULL);
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.
- WebBrowser Control Overviews and Tutorials (MSDN)
- Reusing the WebBrowser Control (MSDN)
- WebBrowser Object (MSDN)
- WebBrowser Customization (MSDN)
- About MSHTML (MSDN)
- Reusing MSHTML (MSDN)
- MSHTML Reference (MSDN)
- WebBrowser Customization, paragraph: WebBrowser Customization Architecture (MSDN)
- WebBrowser Customization, paragraph: How It Works (MSDN)
- WebBrowser Customization, paragraph: IDocHostUIHandler::ShowContextMenu (MSDN)
- Implementing IUnknown in C++ (MSDN)
- IUnknown Interface (MSDN)
- IOleClientSite Interface (MSDN)
- IDocHostUIHandler Interface (MSDN)
- IDocHostUIHandler::ShowContextMenu (MSDN)
- Using Menus, paragraph: Displaying a Shortcut Menu (MSDN)
- TrackPopupMenu Function (MSDN)
- GetSubMenu Function (MSDN)
- DeleteMenu Function (MSDN)
- Creating a Web Browser-Style MFC Application (MSDN)
- CHtmlView Class (MSDN)
- How to Modify-Remove the context menu shown by an IE WebBrowser Control (Code Project)