The MSHTML Element Events2 interface includes a set of methods that allow you to sink events when hosting the WebBrowser control or automating Internet Explorer in your custom code.
This article introduces the MSHTML Element Events2 interface and describes the steps required to sink an event from the browser using Microsoft® Visual Studio® 5.0 or later.
This article is divided into the following sections:
The IWebBrowser2::Navigate2 method of IWebBrowser2 interface allows you to navigate the browser to any URL. Since the page needs to be loaded prior to accessing the document pointer, you should access the document pointer in your event handler for the DocumentComplete event of the WebBrowser control. This event is fired once for each frame in the page, and once when the top frame of the document is fully loaded. You can distinguish the DocumentComplete event for the top frame from others by comparing the parameter passed to the Web browser's IDispatch pointer.
Your DocumentComplete event should be similar to the following sample code:
STDMETHODIMP myObject::DocumentComplete(IDispatch* pDisp, VARIANT* URL) { // DocumentComplete is fired for each frame that has completed loading. // The outer most frame will fire last. if(m_spWebBrowser == pDisp) // member attribute, m_spWebBrowser of type IHTMLDocument2 { // having sunk the DocumentComplete event, // now do your processing for other events here. } return S_OK; }
The preceding code assumes that the following members are defined in your header file and populated during initialization of the object.
public: SHDocVw::IWebBrowserPtr m_spWebBrowser; DWORD m_dwAdviseCookie;
The DocumentComplete event handler in the previous sample code used a smart pointer to compare the Web browser's m_spWebBrowser interface pointer with the pDisp pointer provided. For this reason comparing the IDispatch pointer to the browser's IHTMLDocument2 pointer will work; however, you might cause errors when deleting the pointer since the smart pointer has been implicitly cast from an IDispatch type of pointer to an IHTMLDocument2 type of pointer.
Smart pointers are implicitly converted to a correct type of pointer for use in comparison operators, such as "==", and for use in pointer arithmetic; however, the implicit conversion could cause trouble for the novice. If you are not familiar with creating and using smart pointers, you might use a regular pointer, as shown in the following code:
void myObject::OnDocumentComplete(LPDISPATCH pDisp, VARIANT * URL) { IUnknown* pUnk; LPDISPATCH m_spWebBrowser; //local variable HRESULT hr; pUnk = m_spWebBrowser.GetControlUnknown(); ASSERT(pUnk); hr = pUnk->QueryInterface(IID_IDispatch, (void**)&m_spWebBrowser); ASSERT(SUCCEEDED(hr)); if (pDisp == m_spWebBrowser ) { // Top-level Window object, so document has been loaded TRACE("Web document is finished downloading\n"); } m_spWebBrowser->Release(); pUnk->Release(); }
When you implement the IOLEObject interface in your code, the IOLEObject interface's SetClientSite method is called from the MSHTML component of the browser, and a site pointer is passed, which is then cached in the member property m_spClientSite. This is demonstrated in the following code:
HRESULT myObject::SetClientSite(IOleClientSite *pClientSite) { HRESULT hr = S_OK; if(NULL != pClientSite) { ATLTRACE(_T("SetClientSite called\n")); if(m_spClientSite) m_spClientSite->Release(); m_spClientSite = pClientSite; // This AddRefs the client site } else //if a replacement pointer, delete the old and release it. { _ASSERT(0 != m_dwAdviseCookie); _ASSERT(m_spWebBrowser != NULL); ATLTRACE(_T("SetClientSite NULL called\n")); hr = AtlUnadvise( m_spWebBrowser, __uuidof(SHDocVw::DWebBrowserEvents2), m_dwAdviseCookie ); m_spWebBrowser.Release(); returnS_OK; }
Having obtained the ClientSite pointer, you can QueryService for a pointer to the the Web browser. Using the Web browser's pointer, you can request to be notified of browser events using the DWebBrowserEvents2 interface as shown in the following code:
try { spSP->QueryService(__uuidof(SHDocVw::IWebBrowserApp), __uuidof(SHDocVw::IWebBrowser2), (void**)&m_spWebBrowser); hr = AtlAdvise(m_spWebBrowser, GetControllingUnknown(), __uuidof(SHDocVw::DWebBrowserEvents2), &m_dwAdviseCookie); } catch(...) { return S_FALSE; } return hr;
You can access the IWebBrowser2::Document interface using the IWebBrowser2::get_Document method of the browser interface, as shown in the following code:
MSHTML::IHTMLDocument2Ptr spDocument(m_spWebBrowser->get_Document());
Using the IHTMLDocument2 interface pointer, you can then request a collection of all elements in the HTML document with the IHTMLDocument2::get_all method, as the following code shows:
MSHTML::IHTMLElementCollectionPtr spAllElements = spDocument->get_All();
The IHTMLDocument2::get_all method returns an IHTMLElementCollection interface. You can use the IHTMLElementCollection interface to call the IHTMLElementCollection::item method and pass the name of the element of interest as a parameter, as shown in the following code:
MSHTML::IHTMLDocument2Ptr spDocument(m_spWebBrowser->get_Document()); MSHTML::IHTMLElementCollectionPtr spAllElements = spDocument->get_All(); _variant_t va("myTagID", VT_I4); MSHTML::IHTMLElementPtr spAnElement; spAnElement = spAllElements->item(va);
The following code can be used as an example of how to iterate the collection of all elements on the page and handle an event for all tags of a certain type, such as OBJECT.
for(long i = 0; i < spAllElements->Getlength(); i++) { _variant_t va(i, VT_I4); spAnElement = spAllElements->item(va); _bstr_t bstrIsControl("OBJECT"); if(bstrTagName == bstrIsControl) { // This will get you any ActiveX controls in a page. It is possible // to call methods and properties of the control off the IHTMLElementPtr. IDispatchPtr pDispatch(spAnElement); HRESULT hr = pDispatch->GetIDsOfNames(IID_NULL, rgwzCaption, 1, LOCALE_SYSTEM_DEFAULT, rgdispidCaption); if(SUCCEEDED(hr)) { // You would call Invoke at this point } } } //for
Sinking events in a C++ application involves the following five steps:
These steps may not be apparent if you are developing in C++ using Microsoft Foundation Classes (MFC) or using Active Template Library (ATL). You can sink events using either MFC or ATL.
MFC provides a number of macros that you can use when sinking events, whether you are automating a server or hosting a control. This article discusses how to sink events in MFC for both cases.
Whether you are automating a COM object or hosting a control, remember that the class you are going to use for the event sink must inherit either directly or indirectly from CCmdTarget. CCmdTarget implements the IDispatch interface used for sinking events.
In addition, you must call EnableAutomation to initialize the IDispatch interface contained within CCmdTarget. Sinking events in MFC when automating a COM object is relatively easy. All you have to do is call AfxConnectionAdvise to advise the connection point that you want to receive events. When you no longer want to receive events, call AfxConnectionUnadvise. These two functions are defined in Afxctl.h. AfxConnectionAdvise takes care of querying for the connection point container, finding the connection point, and advising the connection point.
Remember that for your event sink to receive events, it must inherit in some way from CCmdTarget. CCmdTarget uses a dispatch map to determine which event handler function to call when it receives an event. You must first declare this dispatch map in your header file and then initialize it in your implementation file. Fortunately, MFC provides a set of macros that you can use to create and initialize the dispatch map.
To create a dispatch map, simply specify the DECLARE_DISPATCH_MAP macro in the header file for your event sink class. This macro defines the dispatch map and a few functions that CCmdTarget will use to access the map.
To initialize your dispatch map, place a few macros in the implementation file for your event sink map. The BEGIN_DISPATCH_MAP macro takes the name of your event sink class and the name of its base class. For example, if the name of your event sink class is CEventSink and it inherits from CCmdTarget, the BEGIN_DISPATCH_MAP macro will look like the following:
BEGIN_DISPATCH_MAP(CEventSink, CCmdTarget)
Next, you must specify the events that you want to handle and the functions that will be used to handle the events. To specify these events by their DISPID, use the DISP_FUNCTION_ID macro. This macro takes six parameters. If you want to handle the DownloadComplete event, use the DISP_FUNCTION_ID macro like the following sample code:
DISP_FUNCTION_ID(CIE4Events, "DownloadComplete", DISPID_DOWNLOADCOMPLETE, OnDownloadComplete, VT_EMPTY, VTS_NONE)
The final step is closing your dispatch map. Just use the END_DISPATCH_MAP macro. The completed dispatch map will look like the following sample code:
BEGIN_DISPATCH_MAP(CEventSink, CCmdTarget) DISP_FUNCTION_ID(CIE4Events, "DownloadComplete", DISPID_DOWNLOADCOMPLETE, OnDownloadComplete, VT_EMPTY, VTS_NONE) END_DISPATCH_MAP()
To summarize, the three steps to initialize your dispatch map are:
Sinking events when hosting a control is similar to sinking them when you are automating a COM object. The main difference is that you don't have to Advise or Unadvise the connection point, because CCmdTarget takes care of this for you. In the case of hosting a control, CCmdTarget uses an event sink map instead of a dispatch map, and MFC provides macros to define and initialize this event sink map.
Declaring the event sink map is just like declaring the dispatch map: simply specify the DECLARE_EVENTSINK_MAP macro in your header file. You can also use this macro to declare functions that CCmdTarget will use to access the map. Next, initialize the event sink in your implementation file. You begin the event sink map with the BEGIN_EVENTSINK_MAP macro. When using this macro, you must specify the name of your class and its base class.
Next, specify all the events you want to handle and their event handlers by using one of the ON_EVENT_XXX macros. In most cases, you will just use ON_EVENT. If you want one member function to handle a range of events, you use ON_EVENT_RANGE. The ON_EVENT macro takes five parameters. For example, if you want to handle the Internet Explorer 5 DownloadComplete event, you would specify the following for the ON_EVENT macro:
ON_EVENT(CMyDlg, IDC_WEBBROWSER, DISPID_DOWNLOADCOMPLETE, OnDownloadComplete, VTS_NONE)
Finally, you must close the event sink map with the END_EVENTSINK_MAP macro. Therefore, your complete definition of the event sink map would look like the following:
BEGIN_EVENTSINK_MAP(CMyDlg, CDialog) ON_EVENT(CMyDlg, IDC_WEBBROWSER, DISPID_DOWNLOADCOMPLETE, OnDownloadComplete, VTS_NONE) END_EVENTSINK_MAP()
Note that if you are creating a dialog-based application and have placed the WebBrowser control on your dialog, you can use ClassWizard to create your event sink map and map your event handler functions.
You can sink events from Internet Explorer using the following C++ code. The ConnectEvents method is called when you want to establish the event sink. The Exit method is called when you exit the program. Also, the class CmyClass inherits from IDispatch and the m_pIE data member is an interface pointer for an instance of Internet Explorer that was created through a call to CoCreateInstance, as shown in the following code:
void CmyClass::ConnectEvents() { IConnectionPointContainer* pCPContainer; // Step 1: Get a pointer to the connection point container HRESULT hr = m_pIE->QueryInterface(IID_IConnectionPointContainer, (void**)&pCPContainer); if (SUCCEEDED(hr)) { // m_pConnectionPoint is defined like this: // IConnectionPoint* m_pConnectionPoint; // Step 2: Find the connection point hr = pCPContainer->FindConnectionPoint( DIID_DWebBrowserEvents2, &m_pConnectionPoint); if (SUCCEEDED(hr)) { // Step 3: Advise hr = m_pConnectionPoint->Advise(this, &m_dwCookie); if (FAILED(hr)) { ::MessageBox(NULL, "Failed to Advise", "C++ Event Sink", MB_OK); } } pCPContainer->Release(); } } void CmyClass::Exit() { // Step 5: Unadvise if (m_pConnectionPoint) { HRESULT hr = m_pConnectionPoint->Unadvise(m_dwCookie); if (FAILED(hr)) { ::MessageBox(NULL, "Failed to Unadvise", "C++ Event Sink", MB_OK); } } }
ATL is a library of template classes that you can use to create COM-based applications in C++. ATL makes it easy for you to create Microsoft® ActiveX® controls, and provides default implementations of certain COM interfaces. ATL also gives you two functions that make it easy to sink events for any connectable object: AtlAdvise and AtlUnadvise.
AtlAdvise tells a connectable object that you want to receive events from it. This function takes care of the first three steps of event sinking previously outlined, saving you a lot of time and effort. Just like the IConnectionPoint::Advise method, AtlAdvise returns a cookie that will be used in a call to AtlUnadvise. AtlUnadvise tells the connectable object that you no longer want to receive events, if you have an ATL application that is automating Internet Explorer 5. To tell Internet Explorer 5 that you want to receive events, you would make the following call to AtlAdvise:
HRESULT hr = AtlAdvise(m_spIE, GetUnknown(), DIID_DWebBrowserEvents2, &m_dwCookie);
Four parameters were passed to AtlAdvise in the preceding code. The first is a pointer to the IDispatch of the connectable object. The running instance of Internet Explorer 5 that you are automating is represented by m_spIE, which is a smart pointer. Passing a smart pointer to the Advise method in this way causes the smart pointer to return a pointer to its internal object, IWebBrowser2 in this case. A pointer to IWebBrowser2 will be converted to a pointer to IUnknown when passed to a method that accepts an IUnknown pointer. This is due to the fact that IWebBrowser2, like all COM objects, inherits from IUnknown.
The second parameter must be a pointer to the IUnknown interface of the object that represents the event sink. You can use the GetUnknown method to return this pointer. Remember that to sink events for Internet Explorer 5, the class that represents the event sink must implement IDispatch in some way. In this case, the class is inheriting from IDispatch.
The third parameter is the IID of the connection point. The IID of the connection point for Internet Explorer 5 events is DIID_DWebBrowserEvents2. The last parameter, DWebBrowserEvents2, is a pointer to a DWORD that stores a cookie that will be used in a call to AtlUnadvise. Everything else is the same as if this were a normal C++ application. The client must implement IDispatch::Invoke to handle the events that are fired by Internet Explorer. When you are finished receiving events from Internet Explorer, call AtlUnadvise. For this example, the call to AtlUnadvise would look like the following:
HRESULT hr = AtlUnadvise(m_spIE, DIID_DWebBrowserEvents2, m_dwCookie);
The following article provides information about MSHTML Events.
The following article provides information about Microsoft® Visual Studio®.
The following articles provide information about COM.