CHtmlView 和 Webbrowser 控件中超链接点击事件的获取与检测

2008-10-25 14:02:34 旧日重来

在自己的程序中使用 CHtmlView 或直接嵌入 Webbrowser 控件显示网页时,常常需要获取网页元素的一些事件,以实现对网页显示的控制或与网页元素进行交互。最常见的莫过于获取用户对网页上超链接的所有点击事件。要实现这个需求,在 MSDN 中描述了接收网页元素事件的基本方法,但这篇文章的一些细节语焉不详,让人摸不着头脑。在 CodeProject 这篇文章中提出了一种替代的方法,但是存在一些小的限制。本文详细演示了如何实现在 CHtmlView 中监视链接点击的方法。

为了实现监视所有的链接点击而不是特定的超链接,第一步需要在 CHtmlView 的 IHTMLDocument 上安装全局的 EventHandler 以接收 DISPID_HTMLELEMENTEVENTS2_ONCLICK(或 DISPID_HTMLDOCUMENTEVENTS2_ONCLICK)鼠标点击事件;接下来在事件处理代码中判断是否是在超链接上发生。在处理事件响应函数的安装和卸载时要格外小心,重复安装可能导致重复接收到消息甚至程序崩溃,而忘记卸载则会导致 COM 资源泄漏。

接收网页事件

要响应网页事件,需要实现 IDispatch 接口,并在其 Invoke() 方法中处理接收到的消息。对于 MFC,因为 CCmdTarget 类已经实现了 IDispatch 接口,因此继承 CCmdTarget 并结合相关宏可以较简单的处理消息。代码如下:

// DocEvtHandler.h
// SDocEvtHandler 消息处理类声明 by 旧日重来

#pragma once
#import <mshtml.tlb>

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

  // 消息处理函数
  void OnClick(MSHTML::IHTMLEventObjPtr pEvtObj);

  DECLARE_MESSAGE_MAP()
  DECLARE_DISPATCH_MAP()
  DECLARE_INTERFACE_MAP()
};
// DocEvtHandler.cpp
// SDocEvtHandler 消息处理类实现 by 旧日重来

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

IMPLEMENT_DYNAMIC(SDocEvtHandler, CCmdTarget)

SDocEvtHandler::SDocEvtHandler()
{
  EnableAutomation();  // 重要:激活 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)
{
  // 鼠标点击处理代码...详见下节
}

接下来,在 DocumentComplete 事件中安装事件处理响应函数:

// 事件响应函数的管理 by 旧日重来
///////////////////// .h //////////////////////
class SWebpageView : public CHtmlView
{
  // 其他代码...
  SDocEvtHandler *m_pEventHandler;
  DWORD m_dwDocCookie;    // 用于卸载事件响应函数
  IDispatch *m_pDispDoc;  // 用于卸载事件响应函数
};

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

// 安装响应函数。省略了一些失败判断以突出主要步骤
void SWebpageView::InstallEventHandler()
{
  if(m_dwDocCookie)   // 已安装,卸载先。最后一次安装的才有效
    UninstallEventHandler();

  m_pDispDoc = GetHtmlDocument();
  IConnectionPointContainerPtr pCPC = m_pDispDoc;
  IConnectionPointPtr pCP;
  // 找到安装点
  pCPC->FindConnectionPoint(DIID_HTMLDocumentEvents2, &pCP);
  IUnknown* pUnk = m_pEventHandler->GetInterface(&IID_IUnknown);
  //安装
  HRESULT hr = pCP->Advise(pUnk, &m_dwDocCookie);
  if(!SUCCEEDED(hr))  // 安装失败
    m_dwDocCookie = 0;
}

// 卸载响应函数。省略了一些失败判断以突出主要步骤
void SWebpageView::UninstallEventHandler()
{
  if(0 == m_dwDocCookie) return;

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

// 在 DocumentComplete 事件中安装响应函数
void SWebpageView::OnDocumentComplete(LPCTSTR lpszURL)
{
  // 其他代码...
  InstallEventHandler();
}

// 在 BeforeNavigate2 和 Destroy 事件中卸载响应函数
void SWebpageView::OnBeforeNavigate2(/* ... */)
{
  UninstallEventHandler();
  // 其他代码...
}
void SWebpageView::OnDestroy()
{
  UninstallEventHandler();
  CHtmlView::OnDestroy();
}

在正确安装了事件响应函数之后,就可以接收网页事件了。

检测超链接点击事件

全局事件处理接口成功安装后,当 Webbrowser 控件中有相应的事件发生,则会自动调用事件响应函数。在上面的情况下,会接收到网页中所有的鼠标点击事件,因此我们需要判断用户是否是点击超链接对象。因为超链接内部可能还包含有子结构,例如图像,因此鼠标点击事件不一定直接发生在超链接对象,因此需要根据事件发生的对象逐级向上检查,代码如下:

void SDocEvtHandler::OnClick(MSHTML::IHTMLEventObjPtr pEvtObj)
{
  MSHTML::IHTMLElementPtr pElement =
    pEvtObj->GetsrcElement(); // 事件发生的对象元素
  while(pElement) // 逐层向上检查
  {
    _bstr_t strTagname;
    pElement->get_tagName(&strTagname.GetBSTR());
    if(_bstr_t("a") == strTagname || _bstr_t("A") == strTagname)
    {
      // 已找到 "a" 标签,在这里写相应代码
      // 例1:取得目标地址:
      _variant_t vHref = pElement->getAttribute("href", 0);
      // 例2:取消点击,禁止转到目标页面
      pEvtObj->put_returnValue(_variant_t(VARIANT_FALSE, VT_BOOL));
      break;
    }
    pElement = pElement->GetparentElement();
  }
}

查看:原文地址;来源:live.aulddays.com


注意:本站所有文章除特别说明外均为原创,版权所有,转载请务必以超链接方式注明作者出处,并禁止用作商业用途