更新(从插件发出事件)
好的,所以与gf's附加评论,我发现所需的机制是相反的 - 从 DOM 外部提供的组件发出事件。
这与处理 MSHTML 事件的方式类似,但插件的对象需要使用不同的机制检查提供给它的对象。
在提供事件(或支持为事件附加对象)的对象上,通过 IDispatch(如果需要,还可以通过双接口)提供 AttachEvent 和 detachEvent 方法。如果您希望它与 HTML 元素无缝显示,那么您应该以与 IHTMLElement 上提供的相同方式声明它们。 DISPID 不必匹配,但参数和类型的顺序应该匹配。
AttachEvent 方法 (IHTMLElement2) @ MSDN http://msdn.microsoft.com/en-us/library/aa703974%28VS.85%29.aspx
detachEvent 方法 (IHTMLElement2) @ MSDN http://msdn.microsoft.com/en-us/library/aa703981%28VS.85%29.aspx
(来自平台SDK中的MSHTML.IDL)
[id(DISPID_IHTMLELEMENT2_ATTACHEVENT)] HRESULT attachEvent(
[in] BSTR event,
[in] IDispatch* pDisp,
[retval, out] VARIANT_BOOL* pfResult);
[id(DISPID_IHTMLELEMENT2_DETACHEVENT)] HRESULT detachEvent(
[in] BSTR event,
[in] IDispatch* pDisp);
当您通过 AttachEvent 收到调用时,您需要将事件名称与收到的对象关联起来。同样,当您通过 detachEvent 收到调用时,您需要清除对象与事件名称的关联。
当您希望发出事件时,请检查您存储的所有对象,以查找与您的事件相匹配的应调用的方法。理论上,您不必使用与事件名称相同的方法名称,但实际上,如果这样做,维护和管理会更容易。首先检查 IDispatch 本身,调用 GetIDsOfNames() 来查找与您的事件完全匹配的内容。如果没有,请检查 IDispatchEx 并通过 GetDispID() 查找与您的事件匹配的 Expando 方法。
IDispatch 接口@MSDN http://msdn.microsoft.com/en-us/library/ms221608.aspx
IDispatch::GetIDsOfNames @ MSDN http://msdn.microsoft.com/en-us/library/ms221306.aspx(定位接收者事件方法)
IDispatchEx 接口@MSDN http://msdn.microsoft.com/en-us/library/sky96ah7%28VS.80%29.aspx
IDispatchEx::GetDispID @ MSDN http://msdn.microsoft.com/en-us/library/wwazwk2k%28VS.85%29.aspx(定位接收者事件方法)
最后,一旦找到其中一个处理程序,就调用关联的 Invoke() 方法。
IDispatch::调用@MSDN http://msdn.microsoft.com/en-us/library/ms221479.aspx
IDispatchEx::InvokeEx @ MSDN http://msdn.microsoft.com/en-us/library/asd22sd4%28VS.85%29.aspx
初始(处理预定义的 MSHTML 事件)
大多数事件处理程序对象都是手动创建的,因为这允许它们在通过attachEvent()接收调用或分配给事件属性时通过IDispatch接受MSHTML事件。此机制不同于 COM 中流行的典型 ConnectionPointContainer 和 EventSink 设置。然而,创建一个对象来处理这些事件更简单。如果您要创建这样的对象来处理事件,则应该注意一些关键差异。
第一个约束是接收到的事件的 DISPID 和方法名称必须与接收到的事件的 DISPID 和方法匹配。关于这一点的文档有些稀疏,但解析正确 DISPID 的最佳位置是查看 C++ 头文件。如果您安装了 Microsoft Platform SDK,您可以查看 include 子目录中的文件(MSHTML 调度 ID 的缩写)。它包含所有相关 MSHTML 调度 ID 的列表。
第二个限制是 IE/MSHTML 不会调用基于 vtable 的接口中声明的方法的二进制版本,因此调用将通过 IDispatch::Invoke() 到达。如果您所需的 COM 框架不负责将两种类型的调用路由到代码中的同一处理程序,这对您来说可能是个问题。
要创建处理程序对象,您需要创建支持 IUnknown、IDispatch 和 IObjectSafety 的 COM 对象。 IUnknown 对于其他任何一个接口都是隐式的,但不要忘记 IObjectSafety。
没有特别要求,但您的对象应该是公寓线程对象,以避免编组问题。由于调用是通过 VARIANT 包装器直接调用 IDispatch,因此如果您正在执行需要多个单元的操作或尝试使用自由线程组件,则可能会遇到问题。大多数框架为此模型创建对象或默认建议这种类型(VB6、Delphi、MFC、ATL)。
上述 C++ 头文件中的定义与 IHTMLElement 上列出的项目完全对应。这是一个帮助您入门的具体项目。
首先,HTML DOM 元素的事件关闭。
onclick 属性 (IHTMLElement) @ MSDN http://msdn.microsoft.com/en-us/library/aa752306%28VS.85%29.aspx
我们注意到这个属性的名称是onclick。现在,到头文件。
MSHTMDID.H @ DDART.NET http://doc.ddart.net/msdn/header/include/mshtmdid.h.html
我们想要的匹配项是DISPID_EVMETH_ONCLICK(请注意此处的启发式:调度 ID => 事件方法 => OnClick)。根据源文件,它通过宏定义重用了现有的定义。
#define DISPID_EVMETH_ONCLICK DISPID_CLICK
某些定义重叠或重复使用为一般 ActiveX/OLE 控件使用定义的相同 DISPID。DISPID_CLICK是在 OleCtl.h 中定义的,所以让我们去那里追踪最终值。这个头文件也可以在 Platform SDK 中找到,并且默认情况下也包含在 VC++ 安装中,据我所知,至少可以追溯到 VC++ 6.0。
OLECTL.H @ DDART.NET http://doc.ddart.net/msdn/header/include/olectl.h.html
我们想要的 DISPID 是 -600。
#define DISPID_CLICK (-600)
现在,在组件的 IDL 中,您需要声明一个名为 onclick() 的方法,该方法具有此 DISPID 值;或者您需要在 IDispatch::Invoke() 的处理程序中处理此 DISPID。如果您使用 ATL,声明方法并提供双重布局也没有什么坏处。其他实现可能会有所不同。
开发的其余部分应该与 Internet Explorer 中的脚本对象典型相同。另请注意,大多数 DISPID 都处于负值范围内,以避免与用户定义的 DISPID 发生冲突。