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<CDocumentSink> *pSink;
CComPtr<IUnknown> pSrcUnk;
DWORD dwDocCookie;
STDMETHODIMP CWebOCWindow::DocumentComplete(IDispatch *wbDisp, VARIANT* url) {
HRESULT hr;
CComPtr<IDispatch> pDocDisp;
CComQIPtr<IHTMLDocument2> pDoc;
CComPtr<IUnknown> 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<void **>(&wbDispUnk));
if (FAILED(hr)) {
goto cleanup;
}
if (wbDispUnk == browserUnk) {
hr = CComObject<CDocumentSink>::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 <MSHTML.h> and <MSHTMDID.h> (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