接收事件的途径
依靠开发工具你创建客户应用程序 ,你可以接收事件通过不同的途径. 显然, 在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
1<strstrea.h>
2
3
4 STDMETHODIMP CSomeClass::Invoke(DISPID dispidMember,
5
6
7 REFIID riid,
8
9
10 LCID lcid,
11
12
13 WORD wFlags,
14
15
16 DISPPARAMS* pDispParams,
17
18
19 VARIANT* pvarResult,
20
21
22 EXCEPINFO* pExcepInfo,
23
24
25 UINT* puArgErr)
26
27
28 {
29
30
31 USES_CONVERSION;
32
33
34 strstream strEventInfo;
35
36
37
38
39
40 if (!pDispParams)
41
42
43 return E_INVALIDARG;
44
45
46
47
48
49 switch (dispidMember)
50
51
52 {
53
54
55 // The parameters for this DISPID:
56
57
58 // [0]: URL navigated to - VT_BYREF|VT_VARIANT
59
60
61 // [1]: An object that evaluates to the top-level or frame
62
63
64 // WebBrowser object corresponding to the event.
65
66
67 //
68
69
70 case DISPID_NAVIGATECOMPLETE2:
71
72
73 // Check the argument's type.
74
75
76 if (pDispParams->rgvarg[0].vt == (VT_BYREF|VT_VARIANT))
77
78
79 {
80
81
82 CComVariant varURL(*pDispParams->rgvarg[0].pvarVal);
83
84
85 varURL.ChangeType(VT_BSTR);
86
87
88
89
90
91 // strEventInfo is an object of type strstream.
92
93
94 //
95
96
97 strEventInfo << "NavigateComplete2: "
98
99
100 << OLE2T(vtURL.bstrVal)
101
102
103 << ends;
104
105
106
107
108
109 ::MessageBox(NULL, strEventInfo.str(), "Invoke", MB_OK);
110
111
112 }
113
114
115 break;
116
117
118
119
120
121 default:
122
123
124 break;
125
126
127 }
128
129
130
131
132
133 return S_OK;
134
135
136 }
137
138---
139
140## 在 ATL中接收事件
141
142连同实现了缺省的 COM 接口实现, ATL提供了两个函数— _AtlAdvise_ 和 _AtlUnadvise_ —使得任何课连接对象的事件接收简单化.
143
144_ AtlAdvise _ 函数告诉一个可连接对象客户想从此可连接对象接收事件.该函数封装实现接收事件的步骤1到3. _AtlAdvise_ 理所当然省了大量的时间.就像 _IConnectionPoint::Advise_ 方法, _AtlAdvise_ 返回一个cookie供你稍后调用 _AtlUnadvise_ . _AtlUnadvise_ 告诉可连接对象客户不再接收事件.
145
146让我们行说吧, 举个例子 , ATL应用程序自动化Internet Explorer, 所以你想知道任何IE激发的事件. 为了告知Internet Explorer客户想接收事件,发出对 _AtlAdvise_ 的以下调用:
147
148
149 HRESULT hr = AtlAdvise(m_spInetExplorer, GetUnknown(),
150
151
152 DIID_DWebBrowserEvents2, &m_dwCookie);
153
154---
155
156四个参数传递给 _ AtlAdvise _ . 第一个参数是指向可连接对象的 _IUnknown_ 接口的指针. _m_spInetExplorer_ 数据成员是一个经过我们自动化当前运行的Internet Explorer实例的指针. 因为 _m_spInetExplorer_ 指向的对象直接或者间接继承自 _IUnknown_ , 编译器自动转换 _m_spInetExplorer_ 为当前运行的 IE实例的 _IUnknown_ 接口指针.
157
158_ AtlAdvise _ 第二个参数必须指向提供事件的对象的 _IUnknown_ 接口. _GetUnknown_ 函数返回此接口.记住,提供事件的类必须通过某种途径实现 _IDispatch_ in.在此例子中,该类继承自 _IDispatch_ .
159
160第三个参数为你象接收的事件的 IID, Internet Explorer 事件的可连接对象的IIS是 _DIID_DWebBrowserEvents2_ .
161
162最后一个参数指向 DWORD的指针,该DWORD接收返回的Cookie. 该 cookie 将用于调用 _AtlUnadvise_ .
163
164客户必须实现 _ IDispatch::Invoke _ 以控制Internet Explorer 激发的事件. 当你的应用程序完成从IE接收事件, 只需要调用 call _AtlUnadvise_ , 如下:
165
166
167 HRESULT hr = AtlUnadvise(m_spInetExplorer,
168
169
170 DIID_DWebBrowserEvents2,
171
172
173 m_dwCookie);
174
175---
176
177**  **
178
179** Figure 7-3. ** _ATLIEEvtSpy._
180
181以下展示如何自动化 IE:
182
183
184 hr = CoCreateInstance(CLSID_InternetExplorer, NULL, CLSCTX_LOCAL_SERVER,
185
186
187 IID_IWebBrowser2, (void**)&m_spInetExplorer);
188
189
190 if (SUCCEEDED(hr))
191
192
193 {
194
195
196 m_spInetExplorer->put_Visible(TRUE);
197
198
199 m_spInetExplorer->GoHome();
200
201
202
203
204
205---
206
207接下来 , _AtlAdvise_ 调用以接收事件, 如下:
208
209
210 hr = AtlAdvise(m_spInetExplorer, GetUnknown(),
211
212
213 DIID_DWebBrowserEvents2, &m_dwCookie);
214
215---
216
217_ CIEEvtObj _ 类继承自 _IDispatch_ , __ 所以 _CIEEvtObj_ 类可以作为事件接收对象. _Invoke_ 实现控制事件. 每当Internet Explorer 激发一个事件, 在listBox中显示一个消息.以西为invoke的代码:
218
219
220 STDMETHODIMP CIEEvtObj::Invoke(DISPID dispidMember,
221
222
223 REFIID riid,
224
225
226 LCID lcid,
227
228
229 WORD wFlags,
230
231
232 DISPPARAMS* pDispParams,
233
234
235 VARIANT* pvarResult,
236
237
238 EXCEPINFO* pExcepInfo,
239
240
241 UINT* puArgErr)
242
243
244 {
245
246
247 _ASSERT(m_spInetExplorer);
248
249
250
251
252
253 USES_CONVERSION;
254
255
256 strstream strEventInfo;
257
258
259
260
261
262 if (!pDispParams)
263
264
265 return E_INVALIDARG;
266
267
268
269
270
271 switch (dispidMember)
272
273
274 {
275
276
277 //
278
279
280 // The parameters for this DISPID are as follows:
281
282
283 // [0]: Cancel flag - VT_BYREF|VT_BOOL
284
285
286 // [1]: HTTP headers - VT_BYREF|VT_VARIANT
287
288
289 // [2]: Address of HTTP POST data - VT_BYREF|VT_VARIANT
290
291
292 // [3]: Target frame name - VT_BYREF|VT_VARIANT
293
294
295 // [4]: Option flags - VT_BYREF|VT_VARIANT
296
297
298 // [5]: URL to navigate to - VT_BYREF|VT_VARIANT
299
300
301 // [6]: An object that evaluates to the top-level or frame
302
303
304 // WebBrowser object corresponding to the event
305
306
307 //
308
309
310 case DISPID_BEFORENAVIGATE2:
311
312
313 strEventInfo << "BeforeNavigate2: ";
314
315
316
317
318
319 if (pDispParams->cArgs >= 5
320
321
322 && pDispParams->rgvarg[5].vt == (VT_BYREF|VT_VARIANT))
323
324
325 {
326
327
328 CComVariant vtURL(*pDispParams->rgvarg[5].pvarVal);
329
330
331 vtURL.ChangeType(VT_BSTR);
332
333
334
335
336
337 strEventInfo << OLE2T(vtURL.bstrVal);
338
339
340 }
341
342
343 else
344
345
346 strEventInfo << "NULL";
347
348
349
350
351
352 strEventInfo << ends;
353
354
355 break;
356
357
358
359
360
361 //
362
363
364 // The parameters for this DISPID:
365
366
367 // [0]: Enabled state - VT_BOOL
368
369
370 // [1]: Command identifier - VT_I4
371
372
373 //
374
375
376 case DISPID_COMMANDSTATECHANGE:
377
378
379 strEventInfo << "CommandStateChange: ";
380
381
382
383
384
385 if (pDispParams->cArgs == 0)
386
387
388 strEventInfo << "NULL";
389
390
391 else
392
393
394 {
395
396
397 if (pDispParams->cArgs > 1
398
399
400 && pDispParams->rgvarg[1].vt == VT_I4)
401
402
403 {
404
405
406 strEventInfo << "Command = "
407
408
409 << pDispParams->rgvarg[1].lVal;
410
411
412 }
413
414
415
416
417
418 if (pDispParams->rgvarg[0].vt == VT_BOOL)
419
420
421 {
422
423
424
425
426---</strstrea.h>