Microsoft KB Archive/246247

= How to sink HTML document events for WebBrowser host =

Article ID: 246247

Article Last Modified on 5/11/2006

-

APPLIES TO


 * Microsoft Internet Explorer 4.0 128-Bit Edition
 * Microsoft Internet Explorer 4.01 Service Pack 2
 * Microsoft Internet Explorer 4.01 Service Pack 1
 * Microsoft Internet Explorer 4.01 Service Pack 2
 * Microsoft Internet Explorer 5.0
 * Microsoft Internet Explorer 5.01
 * Microsoft Internet Explorer 5.5

-



This article was previously published under Q246247



SUMMARY
Developers who host the WebBrowser control in either Microsoft Visual C++ or Visual Basic frequently want to sink events for the currently loaded document. This is very handy for providing global behavior that browser authors want to implement across all pages within the user experience, such as grabbing external information on a clicked link or a piece of selected text.

The general strategy for both Visual C++ and Visual Basic is:
 * 1) Sink the WebBrowser control's events (the DWebBrowserEvents2 interface) and capture the DocumentComplete event. Because the document is not guaranteed to be fully loaded until this event fires, all document sinking must be done from here.
 * 2) Within DocumentComplete, retrieve the WebBrowser's document object and sink the HtmlDocumentEvents interface. In Visual Basic, do this using the WithEvents keyword on an object of type HTMLDocument.
 * 3) Handle the event, and return a Boolean value indicating whether you want Internet Explorer to perform its own event processing or cancel the event.
 * 4) Clear the sink in BeforeNavigate2 and in OnDestroy since the last sink won't be cleared when you are killing the application.



Visual C++ (ATL)
The first order of business in Visual C++ is to create your event sinks. Two event sinks must be created: One for DWebBrowserEvents2 for the WebBrowser control, and one for HtmlDocumentEvents (defined in MSHTML.H) for the actual Active Template Library (ATL) document.

The DWebBrowserEvents2 sink can be implemented very quickly using ATL's IDispEventImpl, as described in the following Microsoft Knowledge Base article:

194179 AtlEvnt.exe sample shows how to creates ATL sinks by using the ATL IDispEventImpl and IDispEventSimpleImpl classes

Because there will be a new document every time the user navigates to a new page, you must sink the document events whenever the WebBrowser control throws the DocumentComplete event. Within your DocumentComplete handler, use the following code to sink document events: // Declare these as members of your IWebBrowser2 sink. CComObject *pSink; CComPtr pSrcUnk; DWORD dwDocCookie;

STDMETHODIMP CWebOCWindow::DocumentComplete(IDispatch *wbDisp, VARIANT* url) { HRESULT hr; CComPtr pDocDisp; CComQIPtr pDoc; CComPtr wbDispUnk;

// Sink only the topmost document. Slightly more complex logic will be needed // if you wish to sink multiple pages embedded in a frameset. hr = wbDisp->QueryInterface(IID_IUnknown, reinterpret_cast(&wbDispUnk)); if (FAILED(hr)) { goto cleanup; }   if (wbDispUnk == browserUnk) { hr = CComObject::CreateInstance(&pSink); if (FAILED(hr)) { goto cleanup; }       // Get the current document from the WebBrowser. // If you'll be surfing to sites with frames, and want to avoid sinking all but // the top-level document - i.e., the frameset - make sure to sink only when the // IUnknown obtained from wbDisp and the original IUnknown of the hosted // WebBrowser control are equal. hr = webOC->get_Document(&pDocDisp); if (FAILED(hr)) { goto cleanup; }       hr = pDocDisp->QueryInterface(&pSrcUnk); if (FAILED(hr)) { goto cleanup; }       // If this is not an HTML document (e.g., it's a Word doc or a PDF), don't sink. pDoc = pDocDisp; if (!pDoc) { goto cleanup; }

hr = AtlAdvise(pSrcUnk, pSink, DIID_HTMLDocumentEvents, &dwDocCookie); if (FAILED(hr)) { goto cleanup; }   }

cleanup: // Only smart pointers used - nothing to do here. return hr; } Additional information about sinking DocumentComplete, particularly when frames are involved, can be found in the following Knowledge Base article:

180366 How to determine when a page is done loading in WebBrowser control

Your HTML document event sink must include the files  and  (which defines all of the DISPIDs for HTMLDocumentEvents).

Event handlers for cancelable document events must return a Boolean value in the pvarResult parameter of the IDispatch::Invoke method. A value of VARIANT_TRUE indicates that Internet Explorer should perform its own event processing; a value of VARIANT_FALSE cancels the event. For this reason, you should override Invoke directly instead of using a shortcut implementation like ATL's IDispEventImpl, which will not allow you to alter the pvarResult. To override Invoke successfully in an ATL event sink, see the following article in the Microsoft Knowledge Base:

181277 The AtlSink.exe sample demonstrates how to implement a dispinterface sink by using the Active Template Library (ATL) in Visual C++

The following sink code shows how Invoke can be overridden to handle the Click event (DISPID_CLICK): void OnClick(VARIANT_BOOL *bProcessEvent) { AtlTrace("CDocumentSink:OnClick - Obtained a click on the document\n"); *bProcessEvent = TRUE; }

STDMETHODIMP Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pdispparams, VARIANT *pvarResult,             EXCEPINFO *pexcepinfo, UINT *puArgErr) { HRESULT hr;

if (dispidMember == DISPID_CLICK) { VARIANT_BOOL bEventRet; OnClick(&bEventRet); pvarResult->vt = VT_BOOL; pvarResult->boolVal = bEventRet; hr = S_OK; } else { hr = DISP_E_MEMBERNOTFOUND; }   return hr; } Finally, make sure you unadvise your sink in BeforeNavigate2: STDMETHODIMP CWebOCWindow::BeforeNavigate2(IDispatch *pDisp, VARIANT *url, VARIANT *Flags, VARIANT *TargetFrameName,                            VARIANT *PostData, VARIANT *Headers, VARIANT_BOOL *Cancel) { if (pSrcUnk) { hr = AtlUnadvise(pSrcUnk, DIID_HTMLDocumentEvents, dwDocCookie); pSrcUnk.Detach; }   return S_OK; }

Visual Basic
The process in Visual Basic is the same: Sink document events in the DocumentComplete event for the WebBrowser object. Fortunately, the process of event sinking is shortened by using Visual Basic's WithEvents keyword in cooperation with a variable declaration.

Assuming your WebBrowser is named WebBrowser1 and you added Microsoft HTML Object Library to the project, the following code behaves exactly the same as the C++ code: Dim WithEvents doc As HTMLDocument

Private Sub Form_Load WebBrowser1.Navigate "http://www.microsoft.com/" End Sub

Private Sub WebBrowser1_DocumentComplete(ByVal pDisp As Object, URL As Variant) Dim htm As IHTMLDocument2 On Error Resume Next Set htm = WebBrowser1.Document If Err.Number = 0 Then MsgBox "HREF is " & htm.location.href End If   Set doc = htm End Sub

Private Function doc_onclick As Boolean MsgBox "Clicked the document!" ' Tell IE to continue processing the event. doc_onclick = True End Function

Private Sub WebBrowser1_BeforeNavigate2(ByVal pDisp As Object, URL As Variant, Flags As Variant, _                           TargetFrameName As Variant, PostData As Variant, Headers As Variant, _                            Cancel As Boolean) set doc = Nothing End Sub

