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.

  1. Install the Internet Development SDK
  2. 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.

  3. Create an MFC project with a browser control
  4. 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.

  5. Add required COM and HTML includes
  6. 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>
  7. Load a Web page
  8. In OnInitDialog() call:

          _T("full url to Web page"),
  9. Trap the event that notifies you that the Web page is loaded
  10. 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.

    You shouldn't trap DownloadComplete. Instead, trap DocumentComplete. DocumentComplete passes you an IDispatch of the Web broswer, so that you don't need to get it from the control's member variable. I was getting semi-random unhandled exceptions from MSHTML when I was doing it the other way. Comments and corrections below.

    In CTestBrowserDlg.h:

        // Generated message map functions
        afx_msg void OnDownloadCompleteExplorer();
    afx_msg void OnDocumentCompleteExplorerScreen(LPDISPATCH pDisp, VARIANT FAR* URL);

    In CTestBrowserDlg.cpp:

    BEGIN_EVENTSINK_MAP(CTestBrowserDlg, CDialog)
        ON_EVENT(CTestBrowserDlg, IDC_EXPLORER, 104 /* DownloadComplete */, OnDownloadCompleteExplorer, \
    ON_EVENT(CScreenDlg, IDC_EXPLORER_SCREEN, 259 /* DocumentComplete */, OnDocumentCompleteExplorerScreen, \ VTS_DISPATCH VTS_PVARIANT)
    //}}AFX_EVENTSINK_MAP END_EVENTSINK_MAP() void CTestBrowserDlg::OnDownloadCompleteExplorer() { }
  11. Find the element(s) whose events you want to trap
  12. 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.

  13. Trap the element's event(s)
  14. 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:

    1. Add yourself to the element's connection point interface
    2. 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);


      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.

    3. Implement the element's event interface
    4. In CTestBrowserDlg.h:


      In CTestBrowserDlg.cpp:

      BEGIN_INTERFACE_MAP(CTestBrowserDlg, CCmdTarget)
          INTERFACE_PART(CTestBrowserDlg, DIID_HTMLButtonElementEvents2, Dispatch)
    5. Implement a specific event
    6. In CTestBrowserDlg.h:

      void OnClick(IHTMLEventObj *pEvtObj);

      In CTestBrowserDlg.cpp:

      BEGIN_DISPATCH_MAP(CTestBrowserDlg, CDialog)
          OnClick, VT_EMPTY, VTS_DISPATCH)
      void CTestBrowserDlg::OnClick(IHTMLEventObj *pEvtObj)
[ posted by sstrader on 24 May 2004 at 11:30:26 AM in Programming | tagged mobile development ]