24 May 2004
Trapping element events from a Web Browser control
This entry explains how to trap HTML element events in MFC using the Microsoft Web Control. This will allow you to respond to events such as button clicks and selection changes as they are fired by any element within a Web page.
Setting this up is mostly straightforward. The only tricks are making sure you get the Internet Development SDK from Microsoft's Platform SDK and that you use the correct element type in your HTML.
This was all learned recently, so there are some as yet un-explained caveats.
- Install the Internet Development SDK
- Create an MFC project with a browser control
- Add required COM and HTML includes
- Load a Web page
- Trap the event that notifies you that the Web page is loaded
- Find the element(s) whose events you want to trap
- Trap the element's event(s)
- Add yourself to the element's connection point interface
- Implement the element's event interface
- Implement a specific event
The Internet Development SDK is part of Microsoft's Platform SDK. It contains advanced interfaces (e.g. DIID_HTMLButtonElementEvents2) that will return the event notifications. The earlier interfaces (DIID_HTMLButtonElementEvents) do not. I have not found this documented anywhere, but it was what I discovered in testing.
This is described in detail in the MSDN article "Using MFC to Host a WebBrowser Control." It describes succinctly how to add a Microsoft Web Browser control into a dialog box.
For smart pointers (such as IDispatchPtr or IUnknownPtr), add:
#include <comdef.h>
For the basic HTML interfaces (such as IHTMLEventObj), add:
#include <mshtml.h>
For the basic HTML dispatch defines (such as DISPID_HTMLELEMENTEVENTS2_ONCLICK), add:
#include <mshtmdid.h>
In OnInitDialog() call:
EnableAutomation(); m_browser.Navigate( _T("full url to Web page"), 0, NULL, NULL, NULL);
This wil allow you to iterate through the elements on the page and hook the events you want. You will probably do this completely through the GUI, but here's the code that gets generated.
In CTestBrowserDlg.h:
// Generated message map functions //{{AFX_MSG(CTestBrowserDlg) afx_msg void OnDownloadCompleteExplorer();afx_msg void OnDocumentCompleteExplorerScreen(LPDISPATCH pDisp, VARIANT FAR* URL);DECLARE_EVENTSINK_MAP() //}}AFX_MSG DECLARE_MESSAGE_MAP()
In CTestBrowserDlg.cpp:
BEGIN_EVENTSINK_MAP(CTestBrowserDlg, CDialog) //{{AFX_EVENTSINK_MAP(CTestBrowserDlg) ON_EVENT(CTestBrowserDlg, IDC_EXPLORER, 104 /* DownloadComplete */, OnDownloadCompleteExplorer, \ VTS_NONE)ON_EVENT(CScreenDlg, IDC_EXPLORER_SCREEN, 259 /* DocumentComplete */, OnDocumentCompleteExplorerScreen, \ VTS_DISPATCH VTS_PVARIANT)//}}AFX_EVENTSINK_MAP END_EVENTSINK_MAP() void CTestBrowserDlg::OnDownloadCompleteExplorer() { }
In the DownloadComplete function, loop through all of the page elements and find the ones with the ID you're looking for.
void CScreenDlg::OnDocumentCompleteExplorerScreen(LPDISPATCH pDisp, VARIANT FAR* URL) { // Get the HTML document. //IHTMLDocument2Ptr htmlDoc; //htmlDoc = m_browser.GetDocument(); IWebBrowser2Ptr webBrowser(pDisp); IDispatchPtr htmlDocDisp; (*webBrowser).get_Document(&htmlDocDisp); IHTMLDocument2Ptr htmlDoc(htmlDocDisp);// Get the collection of elements. IHTMLElementCollectionPtr elements; (*htmlDoc).get_all(&elements); IDispatchPtr disp; _variant_t index(0L, VT_I4); do { // Get all elements whose id = "control". (*elements).item(_variant_t("control"), index, &disp); if (disp != NULL) { // Examine their action attribute to determine what should be done. IHTMLElementPtr element(disp); // Add us to the connection points. // ... ++index.lVal; } } while (disp != NULL);
Here, the Web page has button elements with ID = "control". HTML "input" elements do not seem to work. Use "button" elements.
The MSDN Knowledge Base article 181845, HOWTO: Create a Sink Interface in MFC-Based COM Client, explains how to hook the Web element events. Here are the relevant details:
Either call AtlAdvise() and pass your dialog's dispatch interface:
AtlAdvise(m_element, GetIDispatch(FALSE), DIID_HTMLButtonElementEvents2, &m_cookie);
Or, include Afxctl.h, and call AfxConnectionAdvise():
AfxConnectionAdvise(m_element, DIID_HTMLButtonElementEvents2, GetIDispatch(FALSE), FALSE, &m_cookie);
You must disconnect from the object when you're done:
AtlUnadvise(m_element, DIID_HTMLButtonElementEvents2, &m_cookie);
Or:
AfxConnectionUnadvise(m_element, DIID_HTMLButtonElementEvents2, GetIDispatch(FALSE), FALSE, &m_cookie);
Store the elements and cookies in some collection (std::map<>) in order to keep track of them.
In CTestBrowserDlg.h:
DECLARE_INTERFACE_MAP()
In CTestBrowserDlg.cpp:
BEGIN_INTERFACE_MAP(CTestBrowserDlg, CCmdTarget) INTERFACE_PART(CTestBrowserDlg, DIID_HTMLButtonElementEvents2, Dispatch) END_INTERFACE_MAP()
In CTestBrowserDlg.h:
DECLARE_DISPATCH_MAP() void OnClick(IHTMLEventObj *pEvtObj);
In CTestBrowserDlg.cpp:
BEGIN_DISPATCH_MAP(CTestBrowserDlg, CDialog) DISP_FUNCTION_ID(CTestBrowserDlg,"HTMLELEMENTEVENTS2_ONCLICK", DISPID_HTMLELEMENTEVENTS2_ONCLICK, OnClick, VT_EMPTY, VTS_DISPATCH) END_DISPATCH_MAP() void CTestBrowserDlg::OnClick(IHTMLEventObj *pEvtObj) { }
- Techniques after using Swift for a month posted by sstrader on 26 August 2015 at 11:41:51 PM
- Some thoughts on Ruby after finishing a week (minus one day) of training posted by sstrader on 15 December 2011 at 8:59:30 PM
- Links on the singleton pattern posted by sstrader on 9 December 2011 at 9:19:50 AM
- Phonebot posted by sstrader on 29 October 2011 at 6:37:05 PM
- The labeled break in Java posted by sstrader on 4 September 2011 at 11:27:56 AM