接收事件的途径
依靠开发工具你创建客户应用程序,你可以接收事件通过不同的途径. 显然, 在Vb中接收事件同在VC中接收事件相比是如此不同和容易.在 C++ 应用中,你可以用不同的技术,通过使用 ATL, MFC, 或者标准C++.
Visual Basic 中接收事件
Visual Basic是创建大多数类型应用的最轻松的工具, 所以我告诉你VB是处理事件最溶的工具时也不要惊奇. ATL 和 Visual Basic 示例我们同样的工作,但是ATL花费了我4个小时, 而 Visual Basic 例子仅仅只花20 分钟.别说我错了—我是ATL, 和 MFC, C++的忠实信徒, 尤其是你建立一个接口的时候.但是 Visual Basic当建立客户应用程序从类似IE这样的服务器接收事件时是伟大的工具.
OK,如何从Visual Basic 应用程序中接收事件?当宿主WebBrowser 控件,你不必做任何特别的事. Visual Basic 在form上为WebBrowser 控件接收事件.你所需要做的全部事情就是未你要接收的任何事件创建一个事件处理句柄.
你象创建其他事件句柄一样创建句柄 (例如Form_Load event). 从Procedure下拉列表框中选择你象控制的句柄, 在事件句柄中,加入任何你型在事件激发时执行的任何代码.
当自动化服务器时候接收事件, 例如在VB应用中的Internet Explorer,过程直截了当.首先设置对服务器的类型库的引用, 你可以访问Project/References 菜单.之后,采用WithEvents 关键字声明服务器对象的变量.举例, 如果你自动化Internet Explorer, 你将声明变量如下:
下一步,采用new或者其他 关键字创建实例变量 ,如下:
或者:
当你采用以上途径生成实例接收事件, Visual Basic 自动为你初始化和管理事件接收.你不必担心连接点问题,VB为你处理它们.
在你输入建立服务器的代码之后,你插入符合服务器事件的方法调用. 举例来说, 如果你想控制由IE激活的DownloadBegin event, 你应当声明类似如下的方法声明:
' Insert your best Visual Basic code here.
End Sub
当你不再想接收来自服务器的事件,简单设置变量为Nothing:
C++中接收事件
C++ 应用程序中接收事件比Vb中多一些工作.但如果你在MFC对话框程序中宿主过WebBrowser控件, 你可以在classwizard中选择你想控制的事件.使用C++的其他应用程序宿主WebBrowser 或者自动化Internet Explorer 需要多一点的工作,但是仍然不需要更多的工作.在C++客户接收事件,仅仅需要以下5个步骤:
1. 获取连接点容器的指针 (IConnectionPointContainer).
2. 调用 IconnectionPointContainer 的方法 FindConnectionPoint 找出你想接收的事件。对 Internet Explorer 来讲 , 你应当为 DWebBrowserEvents2 连接点接口实现事件 . ( 作为可选 , 你可以调用 EnumConnectionPoints 以枚举服务器支持的全部连接点 )
3. 实现接入你想接收事件的连接点的通报( Advise )。 当实现通告时 , 传递一个事件接收槽的 Iunknown 接口的指针。 记住,事件接收槽必须实现IDispatch 接口以接收来自 WebBrowser 的事件。 Advise 方法将返回一个 cookie ,该 Cookie 在你调用 Unadvise 方法的时候携带上。
4. 实现 IDispatch::Invoke 以控制任何激发的事件。 . ( 开发工具如 MFC 及 ATL 能够容易为你做到 .)
5. 当你不再接受事件,调用 Unadvise , 并且传递 cookie.
以上步骤如果采用VB和MFC \ATL等可能不很明显,但是当你采用标准C++创建应用程序的时候就应当很明显了.
以下 C++ 代码允许你在自动化IE的时候接收事件. 留意注释代码实现了哪一个步骤. 假定当你想连接事件时ConnectEvents 方法被调用,且当应用程序退出时候Exit 方法被调用. 同样的,类 CSomeClass 继承自IDispatch,且m_pIE 数据成员为通过CoCreateInstance 方法创建的IE的实例
{
IConnectionPointContainer* pCPContainer; // Step 1: 获取连接点的指针.
HRESULT hr = m_pIE->QueryInterface(IID_IConnectionPointContainer, ( void**)&pCPContainer);
if (SUCCEEDED(hr))
{
// m_pConnectionPoint is defined like this:
// IConnectionPoint* m_pConnectionPoint;
// Step 2: 选找连接点.
//
hr = pCPContainer->FindConnectionPoint(DIID_DWebBrowserEvents2, &m_pConnectionPoint);
if (SUCCEEDED(hr))
{
// Step 3: 实现连接点地事件接收
//
hr = m_pConnectionPoint->Advise( this, &m_dwCookie);
if (FAILED(hr))
{
::MessageBox(NULL, "Failed to Advise", "C++ Event Sink", MB_OK);
}
}
pCPContainer->Release();
}
}
void CSomeClass::Exit()
{
// Step 5: Unadvise. 注意m_pConnectionPoint 应当在CSomeClass的析构函数中释放
//
if (m_pConnectionPoint)
{
HRESULT hr = m_pConnectionPoint->Unadvise(m_dwCookie);
if (FAILED(hr))
{
::MessageBox(NULL, "Failed to Unadvise", "C++ Event Sink", MB_OK);
}
}
}
注意此处少了step4:客户端的 IDispatch::Invoke 方法实现. 我将很快讨论此点. 每一次服务器激发事件会调用此. 当事件被激发,服务器传递事件的DISPID 到Invoke. 对于 Internet Explorer 5, 以下DISPIDs 定义于ExDispID.h 头文件.
· DISPID_BEFORENAVIGATE2
- DISPID_COMMANDSTATECHANGE
- DISPID_DOCUMENTCOMPLETE
- DISPID_DOWNLOADBEGIN
- DISPID_DOWNLOADCOMPLETE
- DISPID_NAVIGATECOMPLETE2
- DISPID_NEWWINDOW2
- DISPID_ONFULLSCREEN
- DISPID_ONMENUBAR
- DISPID_ONQUIT
- DISPID_ONSTATUSBAR
- DISPID_ONTHEATERMODE
- DISPID_ONTOOLBAR
- DISPID_ONVISIBLE
- DISPID_PROGRESSCHANGE
- DISPID_PROPERTYCHANGE
- DISPID_STATUSTEXTCHANGE
- DISPID_TITLECHANGE
现在我们返回讨论Invoke. 该方法有8个参数, 但我们将仅仅讨论其中的两个: dispidMember 和pDispParams. (其余的参见MSDN中的IDispatch::Invoke.)
dispidMember 参数将告诉你哪一个事件被激发.如果客户应用程序接收来自Internet Explorer的事件, dispidMember 参数的值应当是DISPIDs 列表中的某个.
pDispParams 输入参数是指向容器结构的指针, 存储事件激发时的其他项. 传递到事件句柄的参数存储在pDispParams->rgvarg ,逆序存放. 举例来说, Internet Explorer 激发NavigateComplete2 事件如下所示:
当 Invoke 被调用, pDispParams->cArgs 将包含两个值, URL 参数在 pDispParams->rgvarg[0] 以及pDisp 参数存储在 pDispParams->rgvarg[1]. 这些就是COM次序传递参数给Invoke 方法的方式.
以下为 NavigateComplete2 事件的处理.注意采用ATL的CComVariant 处理从 VARIANT到 BSTR包装.
STDMETHODIMP CSomeClass::Invoke(DISPID dispidMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS* pDispParams,
VARIANT* pvarResult,
EXCEPINFO* pExcepInfo,
UINT* puArgErr)
{
USES_CONVERSION;
strstream strEventInfo;
if (!pDispParams)
return E_INVALIDARG;
switch (dispidMember)
{
// The parameters for this DISPID:
// [0]: URL navigated to - VT_BYREF|VT_VARIANT
// [1]: An object that evaluates to the top-level or frame
// WebBrowser object corresponding to the event.
//
case DISPID_NAVIGATECOMPLETE2:
// Check the argument's type.
if (pDispParams->rgvarg[0].vt == (VT_BYREF|VT_VARIANT))
{
CComVariant varURL(*pDispParams->rgvarg[0].pvarVal);
varURL.ChangeType(VT_BSTR);
// strEventInfo is an object of type strstream.
//
strEventInfo << "NavigateComplete2: " << OLE2T(vtURL.bstrVal) << ends;
::MessageBox(NULL, strEventInfo.str(), "Invoke", MB_OK);
}
break;
default:
break;
}
return S_OK;
}
在ATL中接收事件
连同实现了缺省的COM 接口实现, ATL提供了两个函数—AtlAdvise 和 AtlUnadvise—使得任何课连接对象的事件接收简单化.
AtlAdvise 函数告诉一个可连接对象客户想从此可连接对象接收事件.该函数封装实现接收事件的步骤1到3. AtlAdvise 理所当然省了大量的时间.就像IConnectionPoint::Advise 方法, AtlAdvise 返回一个cookie供你稍后调用 AtlUnadvise. AtlUnadvise 告诉可连接对象客户不再接收事件.
让我们行说吧, 举个例子, ATL应用程序自动化Internet Explorer, 所以你想知道任何IE激发的事件. 为了告知Internet Explorer客户想接收事件,发出对AtlAdvise的以下调用:
|
四个参数传递给AtlAdvise. 第一个参数是指向可连接对象的IUnknown 接口的指针. m_spInetExplorer 数据成员是一个经过我们自动化当前运行的Internet Explorer实例的指针. 因为m_spInetExplorer 指向的对象直接或者间接继承自IUnknown, 编译器自动转换m_spInetExplorer 为当前运行的 IE实例的IUnknown接口指针.
AtlAdvise 第二个参数必须指向提供事件的对象的IUnknown 接口. GetUnknown 函数返回此接口.记住,提供事件的类必须通过某种途径实现 IDispatch in.在此例子中,该类继承自 IDispatch.
第三个参数为你象接收的事件的IID, Internet Explorer 事件的可连接对象的IIS是 DIID_DWebBrowserEvents2.
最后一个参数指向DWORD的指针,该DWORD接收返回的Cookie. 该 cookie 将用于调用 AtlUnadvise.
客户必须实现 IDispatch::Invoke 以控制Internet Explorer 激发的事件. 当你的应用程序完成从IE接收事件, 只需要调用 call AtlUnadvise, 如下:
|
Figure 7-3. ATLIEEvtSpy.
以下展示如何自动化IE:
|
接下来, AtlAdvise 调用以接收事件, 如下:
|
CIEEvtObj 类继承自IDispatch,所以 CIEEvtObj 类可以作为事件接收对象. Invoke 实现控制事件. 每当Internet Explorer 激发一个事件, 在listBox中显示一个消息.以西为invoke的代码:
|
请注意此使用了标准C++ 库的 strstream 类来建立字符串.这么做是因为ATL 不提供像Cstring的类. 每一次从IE接收到事件,建立一个包含事件的名称和参数的字符串. 然后显示在列表框中.
退出时候调用AtlUnadvise:
|
在 MFC中接收事件
MFC提供了数个宏使得你可以接收从自动化的对象或者宿主的控件的事件。在两种情况中, 接收事件的类必须直接或者间接继承自CCmdTarget. CCmdTarget 实现接收事件的IDispatch 接口. 另外, 你必须在你的应用中调用EnableAutomation 初始化包含在CCmdTarget 中的IDispatch.
在MFC中自动化一个COM 对象时接收事件
在mfc中接收事件很容易.全部要做的就是在代码中调用AfxConnectionAdvise 函数以通告连接点客户需要接收事件.当客户不许要接收事件,调用AfxConnectionUnadvise. AfxConnectionAdvise 和 AfxConnectionUnadvise 函数定义于afxctl.h 头文件。
AfxConnectionAdvise 函数查询连接点容器, 寻找可连接点,并且通告连接点. 函数的5个参数如下:
Table 7-3 Parameters of the AfxConnectionAdvise Function
Parameter
|
Description
|
pUnkSrc
|
指向激发事件的 com 对象的 IUnknown 接口的指针 . pUnkSrc 是由 CoCreateInstance . 建立的对象的指针
|
pUnkSink
|
指向事件接收的 IUnknown 接口
|
iid
|
连接点的 IID. 例如对 IE 来说,是 DIID_DWebBrowserEvents2 .
|
bRefCount
|
传递 TRUE 表示建立连接点将导致 pUnkSink 的引用将增加。 FALSE 表示不会增加 .
|
pdwCookie
|
表示此连接。由 AfxConnectionAdvise 将传递给 AfxConnectionUnadvise
|
处理事件也很容易。记住MFC事件接收类必须继承自CCmdTarget. CCmdTarget 使用派遣映射检测当接收到事件时调用处理函数.你必须首先在头文件中声明派遣映射 然后再实现文件中 (.cpp) 实现. 幸运地, MFC提供了宏来帮助声明和处理派遣映射。.
为了定义派遣映射, 首先在声明接收事件类的头文件中简单定义DECLARE_DISPATCH_MAP. 这些宏声明派遣映射和CCmdTarget 访问的函数. 一旦你定义了派遣映射,你应当在实现文件中实现宏. 第一个宏放在BEGIN_DISPATCH_MAP 宏.它指定事件接收类的基础类.举例来说,如果事件类是CEventSink 继承自 CCmdTarget, BEGIN_DISPATCH_MAP 将看起来如下:
接下来用DISP_FUNCTION_ID来声明派遣ID。此宏的六个参数:
Table 7-4 Parameters of the DISP_FUNCTION_ID Macro
Parameter
|
Description
|
theClass
|
事件类的名称
|
szExternalName
|
函数的名字 .
|
dispid
|
事件的 DISPID
|
pfnMember
|
指向处理事件的成员函数 .
|
vtRetval
|
成员函数的返回值类型,是 VARENUM 的每局类型,定义于 wtypes.h 头文件
|
vtsParams
|
空格分隔的参数类型的列表 .
|
假设你想控制DownloadComplete 事件. 告诉CCmdTarget 你将控制处理DownloadComplete, 如下使用:
|
最终采用 END_DISPATCH_MAP宏关闭.完整如下:
|
在MFC中寄宿 ActiveX 控件时处理事件
这类似于处理COM对象的事件.主要区别在于你不需要通告或者解除通告连接点. CCmdTarget 未你控制了他.
在寄宿一个Activex控件情形中, CCmdTarget 使用事件接收宏代理派遣宏.就像你猜想的一样, MFC 提供初始化事件接收通告映射. 声明此宏类似声明派遣宏—派 DECLARE_EVENTSINK_MAP 宏存放在头文件中.另外的声明映射, DECLARE_EVENTSINK_MAP 声明 CCmdTarget 访问映射的类
接下来在类中实现事件接收.开始于EGIN_EVENTSINK_MAP 宏.指定事件接收类的 和它的基类。举例,此处为实例:
现在实用ON_EVENT*宏来处理是按接收.。大多数情形,你将使用带有5个参数的ON_EVENT.携带的参数如下:
|
如果你象多个成员函数处理此事件, 使用ON _EVENT_RANGE宏.
Table 7-5 Parameters of the ON_EVENT Macro
Parameter
|
Description
|
theClass
|
在那个类中接收事件
|
id
|
控件的资源 ID 号
|
dispid
|
有控件激活的事件的 ID.
|
pfnHandler
|
事件的成员函数,用来处理事件句柄 . 此函数应当有 BOOL 来型的返回值以及匹配事件的参数。当事件函数被处理则返回 TRUE
|
vtsParams
|
VTS_ constants 的类型
|
你引刚才用 END_EVENTSINK_MAP 宏.完整的定义如下:
|
对于 DocumentComplete 事件,你应当如下声明:
|
Figure 7-4. MFCIEEvtSpy.
当WebBrowser 控件基于对话框应用,你通常不需要插入默认的宏, 因为 ClassWizard 可为你做这一切.而在SDI或者MDI工程中,需要加上此宏。
现在事件接收映射已经声明, 每当WebBrowser 控件激发了DocumentComplete 事件, OnDocumentComplete 方法将被调用.在CMFCIEEvtSpyDlg的 OnDocumentComplete 方法中, 包含URL和事件名称的字符串被创建。之后字符串加入到列表框中展示WebBrowser 控件的事件.
以下代码解释如何接收处理DocumentComplete 事件.:
|
当启动后,采用CoCreateInstance 创建的ie实例传递LSID_InternetExplorer接口..
以下为代码:
|
为接收Internet Explorer 的事件,你应当声明派遣接口且在实现文中:
|
现在无论如何接收到的自动化 Internet Explorer 的事件 DocumentComplete, OnDocumentComplete方法将被调用. OnDocumentComplete 方法创建一个包含事件名称和URL的字符串,且加入到列表框通告Internet Explorer事件发生.同样期它事件发生也会如此处理.此处为CIE5Events 类的OnDocumentComplete 方法代码:
|