Capturing and tracking mouse click events in CHtmlView or Webbrowser control

2009-05-20 16:53:29 Mingliang Dev

It is often necessary to capture certain object events when dispalying webpages using CHtmlView or Webbrowser controls, so that the webpage display can be controlled or interact. One of the most common task, for example, is to capture all mouse clicks on hyperlinks in the page. For referrence, the MSDN article discribed the basic idea of receiving webpage object events, but not so clear in detail. Alternatively, there is a CodeProject article provieded another approach, but with some limitations. In this article, a detailed demonstration of how to monitor mouse clicks (especially on hyperlinks) in CHtmlView can be found.

To monitor all mouse clicks (other than certain hyperlinks), a global EventHandler has to be installed on the IHTMLDocument interface of the CHtmlView to erceive the DISPID_HTMLELEMENTEVENTS2_ONCLICK (or DISPID_HTMLDOCUMENTEVENTS2_ONCLICK) mouse click event. After that, some filtering is needed in the event handler to find out the hyperlink clicks. The management of the event handler should be very careful: duplicate installation will lead to duplicate message received or even application crash, while forgetting to uninstall will lead to COM resource leaks.

Receiving webpage element events

To receive webpage element events, the IDispatch interface should be implemented and the event handler is provided in the Invoke() method. To achive this in MFC, since CCmdTarget class has implemented the IDispatch interface, it is convenience to just inherit the CCmdTarget class with the help of certain macros. The simplified code is shown blow:

// DocEvtHandler.h
// SDocEvtHandler event handling class declaration by Mingliang Dev

#pragma once
#import <mshtml.tlb>

class SDocEvtHandler : public CCmdTarget
{
  DECLARE_DYNAMIC(SDocEvtHandler)
public:
  SDocEvtHandler();
  virtual ~SDocEvtHandler();

  // Event handler function
  void OnClick(MSHTML::IHTMLEventObjPtr pEvtObj);

  DECLARE_MESSAGE_MAP()

  DECLARE_DISPATCH_MAP()
  DECLARE_INTERFACE_MAP()
};
// DocEvtHandler.cpp
// SDocEvtHandler event handling class implementation by Mingliang Dev

#include "stdafx.h"
#include "DocEvtHandler.h"
#include "mshtmdid.h"

IMPLEMENT_DYNAMIC(SDocEvtHandler, CCmdTarget)

SDocEvtHandler::SDocEvtHandler()
{
  EnableAutomation();  // IMPORTANT: enable the IDispatch
}

SDocEvtHandler::~SDocEvtHandler()
{
}

BEGIN_MESSAGE_MAP(SDocEvtHandler, CCmdTarget)
END_MESSAGE_MAP()

BEGIN_DISPATCH_MAP(SDocEvtHandler, CCmdTarget)
  DISP_FUNCTION_ID(SDocEvtHandler,"HTMLELEMENTEVENTS2_ONCLICK",
  DISPID_HTMLELEMENTEVENTS2_ONCLICK, OnClick,
  VT_EMPTY, VTS_DISPATCH)
END_DISPATCH_MAP()

BEGIN_INTERFACE_MAP(SDocEvtHandler, CCmdTarget)
  INTERFACE_PART(SDocEvtHandler,
  DIID_HTMLButtonElementEvents2, Dispatch)
END_INTERFACE_MAP()

void SDocEvtHandler::OnClick(MSHTML::IHTMLEventObjPtr pEvtObj)
{
  // Mouse click handler codes... Refer to Next section
}

Then, install the event handler in DocumentComplete event:

// Event handler function management by Mingliang Dev
///////////////////// .h //////////////////////
class SWebpageView : public CHtmlView
{
  // Other codes goes here...
  SDocEvtHandler *m_pEventHandler;
  DWORD m_dwDocCookie;    // For uninstallation
  IDispatch *m_pDispDoc;  // For uninstallation
};

//////////////////// .cpp ////////////////////
SWebpageView::SWebpageView()
: m_dwDocCookie(0)
, m_pDispDoc(NULL)
{
  m_pDocHandler = new SDocEvtHandler;
}

// Event handler installation. Some necessary success verification omitted
void SWebpageView::InstallEventHandler()
{
  if(m_dwDocCookie)   // Already installed, uninstall it first. Only the last is valid.
    UninstallEventHandler();

  m_pDispDoc = GetHtmlDocument();
  IConnectionPointContainerPtr pCPC = m_pDispDoc;

  IConnectionPointPtr pCP;
  // Look for the installation point
  pCPC->FindConnectionPoint(DIID_HTMLDocumentEvents2, &pCP);

  IUnknown* pUnk = m_pDocHandler->GetInterface(&IID_IUnknown);
  // Install

  pCP->Advise(pUnk, &m_dwDocCookie);
  if(!SUCCEEDED(hr))  // Failed
    m_dwDocCookie = 0;
}

// Event handler uninstallation. Some necessary success verification omitted
void SWebpageView::UninstallEventHandler()
{
  if(0 == m_dwDocCookie) return;

  IConnectionPointContainerPtr pCPC = m_pDispDoc;

  IConnectionPointPtr pCP;
  pCPC->FindConnectionPoint(DIID_HTMLDocumentEvents2, &pCP);
  hr = pCP->Unadvise(m_dwDocCookie);
}

// Install the event handler in DocumentComplete event
void SWebpageView::OnDocumentComplete(LPCTSTR lpszURL)
{
  // Other codes goes here...
  InstallEventHandler();
}

// Install the event handler in BeforeNavigate2 and Destroy event
void SWebpageView::OnBeforeNavigate2(/* ... */)
{
  UninstallEventHandler();
  // Other codes goes here...
}
void SWebpageView::OnDestroy()
{
  UninstallEventHandler();
  CHtmlView::OnDestroy();
}

The webpage element events can now be received after proper installation of the event handler function.

Detecting the hyperlink click event

After successful installation of the global event handler, the event handler function will be automatically called when corresponding event is fired. In our case, all mouse click event will be received, so we have to filter out the outliers and keep only the hyperlink click events. Since mouse click event is fired only on the the lowest element, but not directly the hyperlink element always (such as an <img> object within <a>), we have to search up along the html DOM tree for the possible hyperlink element. The demo code is shown below:

void SDocEvtHandler::OnClick(MSHTML::IHTMLEventObjPtr pEvtObj)
{
  MSHTML::IHTMLElementPtr pElement =
    pEvtObj->GetsrcElement(); // The element where the event happens
  while(pElement) // Search up along the DOM tree
  {
    _bstr_t strTagname;
    pElement->get_tagName(&strTagname.GetBSTR());

    if(_bstr_t("a") == strTagname || _bstr_t("A") == strTagname)
    {
      // <a> tag found, put your own handler code here:
      // Example 1: retrieve target URL
      _variant_t vHref = pElement->getAttribute("href", 0);
      // Example 2: cancel the hyper-link click, stay in current page 
      pEvtObj->put_returnValue(_variant_t(VARIANT_FALSE, VT_BOOL));
      break;
    }
    pElement = pElement->GetparentElement();
  }
}

View: Original article; From dev.mingliang.org.