接收事件的途径
依靠开发工具你创建客户应用程序,你可以接收事件通过不同的途径. 显然, 在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, 你将声明变量如下:
Dim WithEvents InternetExplorer1 As InternetExplorer
下一步,采用new或者其他 关键字创建实例变量 ,如下::
Set InternetExplorer1 = CreateObject("InternetExplorer.Application.1")
或者:
Set InternetExplorer1 = New InternetExplorer
当你采用以上途径生成实例接收事件, Visual Basic 自动为你初始化和管理事件接收.你不必担心连接点问题,VB为你处理它们.
在你输入建立服务器的代码之后,你插入符合服务器事件的方法调用. 举例来说, 如果你想控制由IE激活的DownloadBegin event, 你应当声明类似如下的方法声明:
Private Sub InternetExplorer1_DownloadBegin()
' Insert your best Visual Basic code here.
End Sub
当你不再想接收来自服务器的事件,简单设置变量为Nothing:
Set InternetExplorer1 = 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的实例
void CSomeClass::ConnectEvents()
{ 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 事件如下所示:
NavigateComplete2(pDisp, URL)
当 Invoke 被调用, pDispParams->cArgs 将包含两个值, URL 参数在 pDispParams->rgvarg[0] 以及pDisp 参数存储在 pDispParams->rgvarg[1]. 这些就是COM次序传递参数给Invoke 方法的方式.
以下为 NavigateComplete2 事件的处理.注意采用ATL的CComVariant 处理从 VARIANT到 BSTR包装.
#include 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: "
rgvarg[5].pvarVal);
vtURL.ChangeType(VT_BSTR);
strEventInfo << OLE2T(vtURL.bstrVal);
}
else
strEventInfo << "NULL";
strEventInfo << ends;
break;
//
// The parameters for this DISPID:
// [0]: Enabled state - VT_BOOL
// [1]: Command identifier - VT_I4
//
case DISPID_COMMANDSTATECHANGE:
strEventInfo rgvarg[1].vt == VT_I4)
{
strEventInfo << "Command = "
rgvarg[0].vt == VT_BOOL)
{
strEventInfo << ", Enabled = "
? "True" : "False");
}
}
strEventInfo << ends;
break;
case DISPID_DOCUMENTCOMPLETE:
strEventInfo << "DocumentComplete" << ends;
break;
case DISPID_DOWNLOADBEGIN:
strEventInfo << "DownloadBegin" << ends;
break;
case DISPID_DOWNLOADCOMPLETE:
strEventInfo << "DownloadComplete" rgvarg[0].pvarVal);
vtURL.ChangeType(VT_BSTR);
strEventInfo << "NavigateComplete2: "
<< OLE2T(vtURL.bstrVal)
<< ends;
}
break;
//
// The parameters for this DISPID:
// [0]: Maximum progress - VT_I4
// [1]: Amount of total progress - VT_I4
//
case DISPID_PROGRESSCHANGE:
strEventInfo rgvarg[1].vt == VT_I4)
{
strEventInfo << "Progress = "
rgvarg[0].vt == VT_I4)
strEventInfo << ", ProgressMax = "
}
strEventInfo {
strEventInfo }
else
{
strEventInfo << "NULL";
}
strEventInfo strEventInfo << "StatusTextChange: ";
if (!strcmp(OLE2T(lpStatusText), ""))
strEventInfo << "NULL";
else
strEventInfo << OLE2T(lpStatusText);
strEventInfo << ends;
break;
case DISPID_NEWWINDOW2:
strEventInfo << "NewWindow2" {
strEventInfo }
else
{
strEventInfo << "NULL";
}
strEventInfo << ends;
break;
// The user has told Internet Explorer to close.
//
case DISPID_ONQUIT:
return Stop();
default:
// Note: This class acts only as an event sink, so
// there's no reason to call the base class version of Invoke.
strEventInfo AddEventToList(CMFCIEEvtSpyDlg::IE5ListBox, strEvt);
}