Once access to the DOM has been achieved, the IE DOM will be used to enumerate all the DOM elements of which we will select one to add an event listener to monitor the onClick event.
It is assumed that a Microsoft Foundation Classes (MFC) application is hosting the WebBrowser control, or launching Internet Explorer.
This article is divided into the following sections:
CHTMlView provides a virtual OnDocumentComplete method which you would create and use to monitor when the document is loaded. Once loaded, you can gain access to the DOM from the CHTMLView HtmlDocument method.
This event may be fired once for each frame in the page, and once when the top frame of the document is loaded.
In this case you would need to access the COM IDispatch interface pointer directly for the WebBrowser control and register a listener for DocumentComplete. When your listener was activated you would be able to compare the IDispatch pointer received to that of the web browser control. If they matched you would know you had the top level document.
For simplicity purposes, this technique will register listeners when the CHTMLView MFC component OnDocumentComplete method is called.The example states that in order to listen to DOM events in Internet Explorer you need to first have a valid document. IE provides an IWebBrowser2 interface with a Navigate2 function. When the Navigate2 function is called with a URL a page is loaded into the browser and displayed.
When the document has been completely loaded the browser component fires an onDocumentComplete event. You cannot access the document until it is fully loaded. This code sample makes use of the CHTMLView COM Component to gain access to the DOM when the Document is loaded. The reference to the DOM IHTMLDocument2 interface is then used for further processing.
class MyCHTMLView :: public CHTMLView { } MyCHTMLView :: OnDocumentComplete( LPCTSTR lpszURL ) { IDispatch * pDocDisp = NULL; // get the DOM pDocDisp = this->GetHtmlDocument(); if (pDocDisp != NULL) { // Obtained the document object by specifying the IHTMLDocument2 Interface. pDocDisp->QueryInterface( IID_IHTMLDocument2, (void**)&pDoc ); if ( SUCCEEDED(hr) ) { // Obtained the IHTMLDocument2 interface for the document object ProcessDocument( pDocDisp ); } pDocDisp->Release(); } }
Using the IHTMLDocument2 interface pointer, you can request a collection of all elements in the HTML document through the IHTMLDocument2::get_all property.
void CMyClass::ProcessDocument(IHTMLDocument2* pDoc) { IHTMLElementCollection* pElemColl = NULL; hr = pDoc->get_all( &pElemColl ); if ( SUCCEEDED(hr) ) { // Obtained element collection. ProcessElementCollection( pElemColl ); pElemColl->Release(); } }
The IHTMLDocument2::get_all property returns a collection of all the HTML elements on the page through an IHTMLElementCollection interface pointer. You can use the IHTMLElementCollection interface to call the IHTMLElementCollection::item method and pass the name or ID of an element as a parameter, as shown in the following code.
Note The item property will return a collection if there is more than one element with the specified name or ID. To prevent a collection from being returned, provide an index as the second parameter of item to specify which element should be returned.
void CMyClass::ProcessElementCollection(IHTMLElementCollection* pElemColl) { IDispatch* pElemDisp = NULL; IHTMLElement* pElem = NULL; _variant_t varID( "myID", VT_BSTR ); _variant_t varIdx( 0, VT_I4 ); hr = pElemColl->item( varID, varIdx, &pElemDisp ); if ( SUCCEEDED(hr) ) { hr = pElemDisp->QueryInterface( IID_IHTMLElement, (void**)&pElem ); if ( SUCCEEDED(hr) ) { // Obtained element with ID of "myID". ConnectEvents( pElem ); pElem->Release(); } pElemDisp->Release(); } }
If you are working with HTML tags of a specific type, such as A tags, the IHTMLElementCollection::tags method returns a collection of all the elements that have the requested HTML tag name, also through an IHTMLElementCollection interface pointer.
Each element in the DHTML Object Model supports an outgoing HTMLElementEvents2 interface. This interface defines the events that an HTML element can fire. You implement this interface to provide an event sink, which is a COM object that implements an outgoing interface and is used as the mechanism for firing events.
Note Interfaces implemented by a server usually have their methods called by the client, but to fire an event, the server calls the respective method on the client event sink. These interface are called outgoing interfaces. A COM object that implements an outgoing interface is also known as a connectable object.
The following steps are required to receive events from an outgoing interface:
The event sink implements the appropriate outgoing interface and methods. Internet Explorer event interfaces are dispinterfaces, so calls to event methods are made through IDispatch::Invoke. This means that you only need to implement the IDispatch interface to handle events.
Call QueryInterface to retrieve a pointer to the IConnectionPointContainer interface.
Call the IConnectionPointContainer::FindConnectionPoint method to find the connection point you need. For Internet Explorer WebBrowser events, such as DocumentComplete, this is DWebBrowserEvents2. For element events, this is HTMLElementEvents2. You can also call the IConnectionPointContainer::EnumConnectionPoints to enumerate through all the connection points a server supports.
Using the IConnectionPoint interface pointer returned in the previous step, call IConnectionPoint::Advise, passing the IUnknown interface pointer of your event sink.
Note The connectable object will use the IUnknown interface pointer to query the client for the event sink interface. If the event sink does not support the outgoing interface, Internet Explorer will query the client for the IDispatch interface.
The following sample code demonstrates how to begin receiving HTML element events for an element on an HTML page.
void CMyClass::ConnectEvents(IHTMLElement* pElem) { HRESULT hr; IConnectionPointContainer* pCPC = NULL; IConnectionPoint* pCP = NULL; DWORD dwCookie; // Check that this is a connectable object. hr = pElem->QueryInterface( IID_IConnectionPointContainer, (void**)&pCPC ); if ( SUCCEEDED(hr) ) { // Find the connection point. hr = pCPC->FindConnectionPoint( DIID_HTMLElementEvents2, &pCP ); if ( SUCCEEDED(hr) ) { // Advise the connection point. // pUnk is the IUnknown interface pointer for your event sink hr = pCP->Advise( pUnk, &dwCookie ); if ( SUCCEEDED(hr) ) { // Successfully advised } pCP->Release(); } pCPC->Release(); } }
The following sample code demonstrates how you would detect the firing of an onclick event in your implementation of IDispatch::Invoke.
STDMETHODIMP CEventSink::Invoke( DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult, EXCEPINFO* pexcepinfo, UINT* puArgErr) { switch ( dispidMember ) { case DISPID_HTMLELEMENTEVENTS2_ONCLICK: OnClick(); break; default: break; } return S_OK; }