如何对webbrowser和IE编程

C and C++ study 专栏收录该内容
55 篇文章 0 订阅

如何对webbrowser和IE编程

 

 

一、

因为工作缘故,需要研究对IE编程,所以翻译了MS的有关资料,供参考。

 

IE的体系

 

WebBrowser Host

首先,必须有COM的基础知识,因为IE本身就是COM技术的典型应用。我们看到最上层是WebBrowser的宿主(Host),也就是任何你想重用(ReUse)webbrowser control的应用程序,可以是vb程序,也可以是vc或者任何其他语言的应用程序。应用程序必须是可容纳activex控件的容器。

Webbrowser control既是activex control也是activex document 的宿主。作为控件,它可以置于任何activex容器,作为文档容器,它针对特殊的类型,调用特殊类型注册的文档server以显示文档。如果你想显示一个.doc文档,webbrowser control装载windows word,,对于html文档,webbrowser装载名为mshtml的组件(如图所示)。针对不同的文档,你不需要开发不同的应用,仅仅是调用webbrowser control即可。

 

Shdocvw

Shdocvw.dll包含了webbrowser control ,控制webbrowser control (就像控制其他任何activex com 控件一样),提供浏览能力给上层宿主。请注意webbrowser control位于第二级中。甚至IE也不直接而是通过shdocw.dll来使用webbrowser control的。尽管shdocvw提供了大部分的浏览功能,但是并不提供IE窗口的呈现功能。尽管如此,shdocvw还是提供了简单创建web 应用程序的能力。之后章节会讲到如何将ie的高级特性加入到你的应用程序。

 

MSHTML

早期的mshtml具有读取显示html的功能。MSHTML实际上是一个active 文档服务器,但是却可以作为其他控件的容器(如图中所示的)。记得吗,你可以将activex控件置于html中,此时mshtml就是一个activex宿主,还可以控制其他的控件如vbscript 脚本引擎和javascript脚本引擎,java applet ,geszhong,各种插件—别意外,plug-in都是按照ie activex规范写的。

 

二、

WebBrowser Control 与 Internet Explorer

二者又太多的共同点,你都是通过COM接口来访问其功能。当调用webbrowser控件时,使用的是webbrowser对象,在vc中是使用class ID CLSID_WebBrowser的接口类。

当自动化ie时,必须建立名为 InternetExplorer的对象,vc等语言中使用名为class ID CLSID_InternetExplorer的接口类。

 

接口

webbrowser有4个接口(如图),其中3个提供全部的功能,第4个DWebBrowserEvents2接口暴露事件

ie4 之前,仅有IwebBrowser和IwebBrowserApp两个接口,且二者共享相同的功能,当开发ie4时候,决定加入第三个接口IWebBrowser2扩展webbrowser的功能。该接口派生于第二个接口。

 

IWebBrowser

IWebBrowser 仅仅是WebBrowser control的最初接口, 提供基本的例如导航web页面的功能。 作为其他派生接口的基础接口,有8个方法和30个属性。

方法

 描述

GoBack

导航到历史列表中的上一个页面

GoForward

导航到历史历表中的下一个项目

GoHome

导航到缺省页面.

GoSearch

导航到缺省搜索页面

Navigate

导航到一个页面或者页面

Refresh

刷新当前页面

Refresh2

可以选择刷新级别,如下:

 

REFRESH_NORMAL 普通刷新,并且不发送HTTP pragma:nocache 到服务器.

 

REFRESH_IFEXPIRED 仅仅当页面失效时才发送刷新请求

 

REFRESH_CONTINUE 内部使用,不要在编程时使用

 

REFRESH_COMPLETELY 发送HTTP pragma:nocache header 到服务器

Stop

停止当前导航

 

尽管WebBrowser control 提供了你需要的导航回退以及前向的方法,但是没有提供存取历史列表的途径. 你可与直接通过属性LocationUR来获取当前页的URL.

在VB或者VC++中调用GoBack GoForward 方法式很容易的.你可以使用WebBrowser control 或者 Internet Explorer对象. 举例来讲, 在VB中你可以如下调用:

WebBrowser1.GoBack           ' Hosting WebBrowser control
InternetExplorer1.GoForward  ' Automating Internet Explorer object

VC++中如下:

m_webBrowser.GoBack();            // Hosting WebBrowser control
m_pInternetExplorer->GoForward(); // Automating Internet Explorer object

尽管 GoBack GoForward 方法十分重要,当控制WebBrowser control或者自动化操作 Internet Explorer, Navigate is 却是最重要的方法.Navigate 允许你导航到特定的你所想的web页或者文件. Navigate 带有5个参数, 允许你指定特定的URL以实现导航以及其他定义导航行为的信息.

第一个参数是 URL, 指示web页的位置和名称或者何处的文件你想装载。 (次擦书类型是BSTR.) URL 可以是你所使用的标准URL 协议类型的一种,例如HTTP 和 FILE, 或者是全路经文件名的本地文件系统, 例如 C:\MyFile.htm.

第二个参数, Flags, 你可指定如何或者甚至何处装载特定的URL. (参数的类型是指向VARIANT的指针) Flags 的值来自名为 BrowserNavConstants 的枚举类型,定义于 ExDisp.h 头文件中且有6个值, 详细解释见下列表. 你可以指定一个或者多个Flags 参数值 (请留意有些当前并未实现).

·         navOpenInNewWindow. 将导致一个新的窗口打开以显示URL. 缺省,如果你在自己的应用程序中控制 WebBrowser control,这些值将导致新的 Internet Explorer 浏览器窗口打开.

  • navNoHistory. 指定此URL将不加入到URL历史列表中
  • navNoReadFromCache. 当前未实现.
  • navNoWriteToCache. 当前未实现.
  • navAllowAutoSearch. 如果指定的URL未找到,自动搜索功能将试图导航到通常的顶级域名如.com .net .org以找到正确的URL。如果失败, URL将传递给搜索引擎.
  • navBrowserBar. 如果可能,将 URL 装入到 Explorer 的地址栏Bar。

第三个参数 TargetFrameName. 指定web页的哪一个frame将会发生导航. (参数的类型是指向VARIANT的指针)此字符串将为以存在于web页中的名字或者一个指定的值如 _top_search_top 值指定 URL 将被当前最顶层web页装入并不在任何一个当前页中存在的frame.  _search 指示搜索面板将被打开. (该值仅在你自动化操控Internet Explorer时发生.) 如果你指定的frame名称并未发现,, 新的Internet Explorer 窗口将被打开。

你可以使用 PostData 参数 (第四个)通过HTTPpost事务将指定特定的数据发送到服务器. (参数的类型是指向VARIANT的指针) Post 处理用于将在HTML表单收集的数据发送到服务器。如果此参数不指定任何数据 ,  Navigate 方法将使用Get方法. 另外,如果你不指定 HTTP 协议的URL (换句话讲,如果你指定例如 FILE 协议的 URL), PostData 参数将被忽略。

你也可以使用第五个参数, Headers, 以发送HTTP 头信息到服务器. (T参数的类型是指向VARIANT的指针) 这些加入的头信息将被 WebBrowser 控件如常发送出去. 作为 PostData 参数, 如果你并不指定第一个参数,Headers 将被忽略.

(二)

调用 Navigate 方法比GoBack 以及GoForward  要困难,但如果你正仅仅导航到URL 且并不传递任何附加参数, 它的调用很容易。 举例来说, VB中导航到 Microsoft 主页, 可如下:

WebBrowser1.Navigate "http://www.microsoft.com"

VB代码调用Navigate 如此容易因为所有参数出第一个参数外都可以是可选. 如此相对照,VC++不可以省略任何参数. 如果你想使用VC++不是勇任何特别的参数导航到 Microsoft的 home page, 你必须传递空的VARIANT结构。 MFC 应用程序,你必须如下调用 Navigate

COleVariant vtEmpty;
m_webBrowser.Navigate(_T("http://www.microsoft.com"), &vtEmpty, &vtEmpty,
                 &vtEmpty, &vtEmpty);

该例子展示了在MFC应用程序中控制WebBrowser控件. 我传递一个普通的字符串而替代BSTR 因为 MFC 提供了一个 WebBrowser 控件的包装类,第一个载包装类中是LPCTSTR, 一个普通的字符串. 其他参数为指向VARIANT 结构的指针。 如果你不想指定任何特别的参数, 别仅仅传递 NULL —那样做, 你的应用程序会招来崩溃。你必须传递一个空的VARIANT结构的指针. 前面的代码使用了 COleVariant 类, 那是一个与 CComVariant 类相似的类。 COleVariant 简单包装了 VARIANT 使得VARIANT 结构易于使用.

属性 现在你看到了所有的IwebBrowser 的方法,你大概向了解其属性。(我是不是太苛求了?)  IWebBrowser 接口有30个属性,描述于表6-2. 我一已按照IWebBrowser  的Vtable中的字母序列出。

Table 6-2. IWebBrowser属性按照vtable排序

属性

描述

Application

返回宿主WebBrowser 控件的自动化对象 (IDispatch) 实现,如果对象不可用,则返回的WebbOrwser控件的自动化对象

Parent

返回WebBrowser控件的父控件的自动化实现 (IDispatch) ,通常是容器—举例来讲,,您的宿主或者Internet Explorer 窗口。

Container

返回WebBrowser控件容器的自动化对象 (IDispatch) 。通常,该值返回同 Parent 属性一样的值。.

Document

返回活动文档的自动化实现 (IDispatch) 。如果 HTML 当前显示在WebBrowser, Document 属性给出你存取 DHTML 对象模型的途径.

TopLevelContainer

返回一个布尔值指示IE是否是WebBRowser控件的顶级容器。在 words中,如果IE是宿主应用承需则返回true

Type

返回已经在WebBrowser中装载的对象的类型。举例,如果HTML document被装载, Type 将返回 Microsoft HTML Document 5.0. 如果文档是Word 文档,Type 返回 Microsoft Word Document.

Left

.返回或设置控件在容器窗口左边距

Top

.返回或设置控件在容器窗口顶部边距

Width

返回或设置webbrowser控件在窗口中的水平方向的向苏为单位宽度

Height

返回或设置webbrowser控件在窗口中的处置方向的向像素为单位高度

LocationName

返回一个WebBrowser当前显示的资源名称的字符串 (换句话讲, HTML 页, Word 文档,,folder, 诸如此类) 。 如果资源是 HTML 页,字符串是标题。如果资源是文件或者文件夹,字符串为文件名或者文件夹名。—举例, foo.doc (Word文档)或者  Temp (temp directory.)

LocationURL

返回WebBrowser正在显示的资源的URL

Busy

返回一个布尔值指示WebBrowser 当前是否正在装入 URL.。如果当前属性返回 true,你可以使用 Stop 方法取消代掉导航。

看完表 6-2, 清晰展示了你可使用的属性. 当中的一些可能需要更多的解释.IWebBrowser 使用的相当多的一个属性是 LocationURL, 给出了当前你装入WebBrowser窗口的 URL。 获取LocationURL 值在VB中相当容易:

Dim strLocation
strLocation = WebBrowser1.LocationURL

Visual C++的MFC应用,  MFC 包装类使的访问LocationURL 很容易. 你简单调用GetLocationURL 方法,该方法返回一个 CString 对象. 当你想在自动化IE中或者webBrowser的MFC应用中访问LocationURL when automating Internet Explorer or when hosting the WebBrowser control in a C++ application that's not built by using MFC, 你必须调用携带一指向BSTR的指针。 BSTR 将包含返回值. 此处展示如何在 C++中使用:

BSTR bstrURL;
m_pInternetExplorer->get_LocationURL(&bstrURL);

 

存取 Document 属性在VB中相当容易,当定义一个变量后(举例,HtmlDoc), 值需要 set 为 Document 属性:

Set HtmlDoc = WebBrowser1.Document

MFC 中也很容易,使用 MFC 包装类的GetDocument方法:

IDispatch* pDisp;
pDisp = m_webBrowser.GetDocument();

GetDocument 返回指向代表文档IDispatch 接口的指针. 如果 GetDocument 失败, 该对象将为NULL.

非MFC C++ 应用或者任何自动化IE的C++ 应用访问Document 属性就只能够采用调用COM的方法—通过属性的get_方法. (注艺属性实只读,所以这里无put_方法.) 当调用 Document  get_ 方法,你传递一个接受 IDispatch 的指针.此处展示了如何调用:

IDispatch* pDisp;
HRESULT hr = m_pInternetExplorer->get_Document(&pDisp);

当使用这些代码,你可以使用 SUCCEEDED 宏( Win32 API的一部分), 以监测调用是否成功。在尝试使用Idispatch接口前你应当确信调用成功。

 

(三)

IWebBrowserApp

IWebBrowserApp 接口仅仅在IE中实现。 典型的,你使用InternetExplorer 对象创建一个IE的实例. 然后你使用IWebBrowserApp 接口操纵此实例. (今天采用IWebBrowser2 替代) IWebBrowserApp 接口继承自IWebBrowser, 所以它提供 IWebBrowser 的全部功能。

因为IWebBrowserApp呈现一个IE窗口的实例, 所以它的方法和属性典型地允许你控制浏览器窗口的用户接口。 这些方法和属性并非包含在WebBrowser的IWebBrowser 中的:寄宿control的应用程序提供诸如状态条, 工具条, 以及菜单条等用户接口. WebBrowser 控件仅仅用于装载web页以及其它类型文件。

IWebBrowserApp 有4个方法和10 各属性。

方法 IWebBrowserApp接口的属性很直观立如下表, 但是有2个有理由值的讨论:GetPropertyPutProperty. 此两个方法允许你在IE属性包(property bag)中存储一个属性一边你能够在其后重新找回他们。 你将典型地从一个web页上存储一些状态信息到另一个web页 (事实上,使用IWebBrowser2 接口你可以在宿主一个WebBrowser 控件时候使用此方法)

Table 6-3. IWebBrowserApp vtable 次序的方法

方法

描述

Quit

促使 Internet Explorer 窗口关闭。换句话讲,将自动化操作关闭IE实例

ClientToWindow

转换一个点从窗口坐标到客户坐标。

PutProperty

存储一个属性值到ie的属性包,将可以随手采用GetProperty.获取

GetProperty

获取先前由 PutProperty.方法存储的属性值

VB代码:

InternetExplorer1.PutProperty "CurrentPicture", 10
InternetExplorer1.GetProperty("CurrentPicture")

VC++代码:

CSomeClass::PutGetProperty(VARIANT vtCurrentValue, VARIANT* vtNewValue)
{
   HRESULT hr;
   hr = m_pInternetExplorer->PutProperty(L"CurrentPicture",
                               vtCurrentValue);
 
   if (SUCCEEDED(hr))
   {
     // Notice that vtNewValue is already a pointer, so you
     // don't have to pass the address to GetProperty.
     //
     hr = m_pInternetExplorer->GetProperty(L"CurrentPicture", 
                                 vtNewValue);
   }
 
   return hr;
}

PutGetProperty 方法接受包含当前属性值的VARIANT 变量指针且将接收属性值。

属性 IWebBrowserApp接口有10 个属性. Table 6-4. IWebBrowserApp vtable 次序的属性

属性

描述

Name

返回对象的名称t. (举例,当自动化IE, Name 属性将返回 Microsoft Internet Explorer.)

HWND

返回IE窗口的句柄

FullName

返回ie可执行文件的全路径 (iexplore.exe).

Path

返回IE应用程序的全路径.

Visible

监测和设置IE窗口是否可见。 (换句话讲,你可与使用此属性显示/隐藏)

StatusBar

显示或者隐藏IE状态条,也可监测当前状态条

StatusText

设置或隐藏状态条文字

ToolBar

显示或者隐藏IE工具条,也可监测当前工具条状态 

MenuBar

显示或者隐藏IE菜单条,也可监测当前工具菜单态 

FullScreen

设置或者检测一个值指示IE当前是否最大化显示。最大化显示,iE占据整个屏幕

Table 6-4 中的属性列表直截了当且易于使用。举例来说, 如果你想从你的Vb程序中获取IE状态条中的文字,你应当使用如下代码:

Dim strStatusText
strStatusText = InternetExplorer1.StatusText

使用MFC包装类的VC++程序代码如下:

BSTR bstrStatusText;
HRESULT hr = m_pInternetExplorer->get_StatusText(&bstrStatusText);

 

 

IWebBrowser2

因为COM规则要求接口恒久不变, 要加入新的工呢高COM,你必须增加新的接口。 新的接口可扩展自其他接口已扩展功能。 举例,当心的需求引入WebBrowser 和Internet Explorer 接口, 开发者被要求建立一个新的接口:IWebBrowser2.

早期, IWebBrowser2 继承自IWebBrowser IWebBrowserApp,还提供不包含在着两个接口中的功能. 所以你应当使用 IWebBrowser2 接口替代 来操纵WebBrowser 控件或者 Internet Explorer.

IWebBrowser2 接口有4个方法和8个属性.

方法   IWebBrowser2 接口方法如下描述,按照 vtable 次序, in Table 6-5.

也许IWebBrowser2 、接口踵使用最多的方法是ExecWB. 它通过WebBrowser实现了一个 IOleCommandTarget 接口的Exec的包装实现。 在 ExecWB 方法 被创建前, 你不可以直接从VB中调用IOleCommandTarget::Exec, 因为VB不可访问IOleCommandTarget 接口 ,因而VB不可直接使用该接口的ExecWB方法。 IOleCommandTarget::Exec方法过去大量使用,所以 WebBrowser 开发者决定创建ExecWB 以使事情变得容易。

 

Table 6-5. IWebBrowser2 Vtable 次序的方法

方法

描述

Navigate2

功能基于Navigate 方法,不同在于 Navigate2 允许你导航到飞URL表达的地方,例如Windows shell folder. (Windows shell folder 是指向标示符指针, 或者windows shell命名空间中的 PIDL,)

QueryStatusWB

IoleCommandTarget接口的QueryStatus方法在 WebBrowser.中的包装实现

ExecWB

IoleCommandTarget接口的Exec方法在 WebBrowser.中的包装实现

ShowBrowserBar

显示或者隐藏特定的浏览器条. 该方法仅仅用于InternetExplorer 对象

 

为什么你无论如何都要调用ExecWB方法?因为它提供了你需要的功能(通过IOleCommandTarget::Exec)不是暴露于webbrowser接口。你大概疑惑于为社么开发者不实现扩展属性和方法。记住:COM规则是一旦接口发表就不可改变。所以你不能够不能增加功能而不创建新的接口。

ExecWB 方法允许WebBrowser 开发者增加新的功能而不用创建新的接口。再之, 那是由WebBrowser 通过ExecWB 代表性的暴露不常用的工的途径, 譬如调用 Save As 对话框或者缩放web页的字体。  ExecWB 工作的方法是传递一个你想调用的command ID 和必需的参数. 太多的 command IDs ,他们包含于OLECMDID 实现文档头文件he DocObj.h .

 

 

作为举例, 支持缩放web页的内容字体。Internet Explorer 允许你通过View菜单的Text Size子菜单改变字体的大小从最小到最大。ExecWB 方法暴露了允许你改变显示在浏览器中的文字大小。对于Zoom 命令, 你可指定特殊值0, 1, 2, 3, or 40 是最小字体 ,4是最大字体。以下举例为改变字体为最大(VB):

WebBrowser1.ExecWB OLECMDID_ZOOM, OLECMDEXECOPT_DONTPROMPTUSER, _
                   CLng(4), Null

再次代码中, 我调用 ExecWB 方法, 传递OLECMDID_ZOOM 常量给第一个参数. 此常量指定 zoom 操作将被执行。 第二个参数传递给ExecWB 不要提示用户。另外的选择, 你可以提醒用户而是用OLECMDEXECOPT_PROMPTUSER. (见 DocObj.h.)

第三个输入参数指示要设定的文字尺寸。我想是文字尽可能的大,所以我指定了4 。注意我在调用中使用了VB的Clng函数包装此值,转换我指定的此值为一个variant—输入需要的类型.最后为一个输出参数包含任何返回值。因为返回值不需要关心,我传递Null.

 

 

属性 IWebBrowser2接口有8个属性。 (Table 6-6 按照vtable次序展示其)。他们都很直观,我仅仅解释恰宏很特别的一个—AddressBar.

Table 6-6. IWebBrowser2Properties in Vtable Order

属性

描述

ReadyState

返回WebBrowser 的状态(换句话讲 ,该属性指示文档是否完成装载). 尽管你可以使用 ReadyState 属性, 使用 DocumentComplete 时间检测文档是否装载还是比较好些。 (那意味着所有HTML和文档被装载

Offline

.返回或者设置检测webbrowser是否处于脱机模式的变量

Silent

返回或设置 WebBrowser 是否处于沉默模式。如果处于沉默模式,意味着没有对话框可以被显示.

RegisterAsBrowser

.设置或者检测Webbrowser是否是作为顶层浏览器被登记

RegisterAsDropTarget

.设置或返回WebBrowser是否作为导航的拖放对象 。如果登记委托放对象,用户可直接拖放连接到浏览器   

TheaterMode

返回或者设置Internet Explorer是否处于theater 或者 normal window 模式.。在theater 模式, Internet Explorer占据整个屏幕就像处于FullScreen 模式,但也有最小化的用户接口元素 (此属性仅仅展示于InternetExplorer 对象)

AddressBar

显示或者隐藏地址栏(此属性仅仅展示于InternetExplorer 对象)

Resizable

返回或者设置Internet Explorer是否可被调整大小, 你可利用此属性防止用户改变webbrowser的大小(此属性仅仅展示于InternetExplorer 对象)

 

 

AddressBar 属性允许你显示/隐藏Internet Explorer 地址栏—一个包含可输入URL的文本框 允许你导航到某个Url。 能够显示或者隐藏地址条允许你完全控制你的用户的导航体验

在你的企业网络或者中小学控制用户导航体验.在此环境下,你将控制用户的导航以至于可确定某些不恰当地页面不可访问。你可创建你自己的浏览器。但是如果你没有时间和资源这么做,你可以自动化IE来代替.自动化IE允许你管掉所有的用户界面允许用户导航到某个web页,像菜单条, 工具条,以地址栏. 之后你可建立一定数量的可访问web页连接列表给用户。

 

关掉这些用户接口很容易. VB代码:

InternetExplorer1.AddressBar = False
InternetExplorer1.ToolBar = False
InternetExplorer1.MenuBar = False

 

 

(四)

寄宿WebBrowser 控件

我们现在开始在VB和VC中创建一些程序来寄宿(Host)WebBrowser控件。在你完成本部分的样本,你将对如何创建寄宿一个WebBrowser控件的应用程序又一个基本的了解。当你看到如此容易的加入Web浏览功能加入到你的应用程序,我相信你会立即开始实践的。

使用VB

在VB中,你可以在5分钟内开发一个全功能的自己的web浏览 。以下步骤为建立一个web浏览器程序:

1.    启动VB.

2.       选择“Standard EXE“ ,进入设计模式。

3.       WebBrowser 控件未自动化包含到控件工具箱(Control Toolbox)。 要增加 WebBrowser 控件到 控件工具箱,选择 Project菜单下的Components. ,如下对话将显示:

 

Figure 6-3.组件对话框.

4.       如果Controls TAB页未显示,点击 Controls TAB页 。然后勾选中列表中的“ Microsoft Internet Controls”,点击OK关闭对话框。Vb将会增加WebBrowser 控件到控件工具箱,如图所示:

 

Figure 6-4.Visual Basic 控件工具条在增加了WebBrowser 控件后的图标

5.       为增加 WebBrowser 控件至窗体,点击WebBrowser 控件,然后确定在窗体中的大小。一旦你增加了一个控件到表单,Visual Basic 将指派其名称为WebBrowser1.

6.       调整表单的尺寸一边导航时候可看到更多的web内容。预留一些空间给地址栏。表单看起来如 Figure 6-5.

7.       双击空区域以增加Load 事件。为了能够使WebBrowser 导航到一个web页,你仅需要调用如GoHome, GoSearch, Navigate,或者 Navigate2.等导航方法。

8.       调用GoHome 方法到用户主页。代码如下:

Private Sub Form_Load()
   WebBrowser1.GoHome
End Sub

Figure 6-5.Visual Basic form after adding the WebBrowser control.

如此就完成了! 你已经创建了一个全功能的web浏览器. 照我的时间,仅仅不好过分钟。为确信它可工作,你可启动进行测试。你的应用程序将装入webbrowser控件兵导航到主页。保存工程为VbWebHost.

尽管你已经拥有一个可工作的Internet应用程序,你仍需要做些工作视你的应用程序更像一个真实的web浏览器。为了可导航增加一些控件到表单。为输入URL增加一个 label, 一个文字输入框, Go 按钮,  Back 按钮,  Forward 按钮, 以及Stop 按钮. 表单应该看起来如Figure 6-6.

如 Table 6-7 分派给你的控件属性。

Figure 6-6.Visual Basic form after adding controls.

Table 6-7. 控件属性

控件

属性

Label

Caption = "Address:"

TextBox

Name = txtAddress; Text = "" (In other words, remove the default text.)

Go Button

Name = btnGo; Caption = "Go"

Back Button

Name = btnBack; Caption = "< Back"

Forward Button

Name = btnFwd; Caption = "Forward >"

Stop Button

Name = btnStop; Caption = "Stop"

增加一些代码,调用WebBrowser 控件的方法使得工作正常。 举例来说,当用户输入文字到文字输入框且点击Go 按钮,使用Navigate 方法处理导航。 当然,你必须缺新用户真实输入了一些文字到文字输入框。

同样, 你也可以使用GoBack, GoForward,以及 Stop 方法以实现Back, Forward, 和 Stop 按钮。记住 GoBackGoForward 方法当前状态下无效。Visual Basic 代码看起来类似如下:

Option Explicit
 
Private Sub btnBack_Click()
   On Error Resume Next
   WebBrowser1.GoBack
End Sub
 
Private Sub btnFwd_Click()
   On Error Resume Next
   WebBrowser1.GoForward
End Sub
 
Private Sub btnGo_Click()
   WebBrowser1.Navigate txtAddress.Text
End Sub
 
Private Sub btnStop_Click()
   WebBrowser1.Stop
End Sub
 
Private Sub Form_Load()
   WebBrowser1.GoHome
End Sub

注意 On Error Resume Next 特别用于Back 和Forward 按钮的click事件处理. 当当前URL前后无历史列表,这些方法将返回错误。Visual Basic错误捕获用于处理他们。

现在测试程序,在浏览器完成导航到主页,输入URL 到文字框点击Go 按钮。你将有两个 URLs 在历史列表中。点击 Back按钮退回到主页, 接着点击 Forward 按钮前移。在web页转载时候点击 Stop 按钮确信Stop 按钮工作正常。

 

 

打印web页

用为用户经常想打印在web浏览器中的页面,你可能细想加入打印功能到你的应用程序。过去打印在VB中笨重难以实现(使用sendkey),现在可以使用ExecWB方法来轻松实现,且非常可靠。

哟增加打印功能,首先加入Print 按钮到表单。 (如前面) 命名按钮为btnPrint, 改变标题为 Print. 看起来如Figure 6-7.

Figure 6-7.Visual Basic form after adding a Print button.

下一步, 双击 Print 按钮以增加Click事件处理代码.事件处理过程中, 调用ExecWB 方法, 传递打印需要的命令ID: OLECMDID_PRINT.如果你想打印前提醒用户, 指定OLECMDEXECOPT_PROMPTUSER; 其他情形指定OLECMDEXECOPT_DONTPROMPTUSER。 本例中,我们打印前提示。打印命令没有输入输出,所以你指定第三个和第四个参数Null 。代码应当如下:

Private Sub btnPrint_Click()
   On Error Resume Next
   WebBrowser1.ExecWB OLECMDID_PRINT, OLECMDEXECOPT_PROMPTUSER, _
                      Null, Null
End Sub

 

 

使用 Visual C++

Vc中创建浏览器应用程序毕vb中稍微困难。如果白手起家创建米的宿应用程序 (无MFC 或 ATL), 需要实现大量的COM接口来寄宿WebBrowser 控件。你必须还要利用COM的APICoCreateInstance  创建WebBrowser 控件的实例,指定 CLSID_WebBrowser a作为你想创建的对象的CLSID 。并且还要 将控件"site"于你的应用程序.

因为建立ActiveX 控件容器费本文讨论主题,所以着重讨论简单途径寄宿webbrowser控件。

使用 MFC

你可以创建3种类型的MFC应用程序: 单文档接口 (SDI), 多文档接口 (MDI), 以及基于对话框的应用程序。因为对话框MFC类似VB,所以本处将讨论采用SDI来寄宿webbrowser控件。一旦你知道如何采用SDI 应用来寄宿webbrowser控件,转为MDI 将会容易些。

在演示SDI的例子中,我将使用WebBrowser (CWebBrowser2) 包装类MFC的内置CHtmlView类将帮助你理解如何在MFC中寄宿WebBrowser 。

CHtmlView 类无需太多解释。要使用它,必须在MFC AppWizard第6步中选择你的应用程序的基类为ChtmlView, 如 Figure 6-8. 在完成 wizard后, 你的应用程序的视图类奖派生自CHtmlView. 然后你可以直接调用IWebBrowser2 接口的不同方法.

Figure 6-8.MFC AppWizard - Step 6 of 6 dialog box.

为创建MFC 单文档接口应用程序,启动Visual C++ 新建菜单。新的对话框展示如 Figure 6-9.

Figure 6-9.Visual C++ New dialog box.

在 Projects 页, 选择 MFC AppWizard (exe) 项, 输入一个项目名称 (譬如MfcWebHost之类), 点击OK. MFC AppWizardis 步骤一显示. (看 Figure 6-10.)

Figure 6-10.Step 1 of the MFC AppWizard.

选择 Single Document 。然后认可缺省选项,点击完成. 要增加WebBrowser 到你的工程,选择菜单Project/Add To Project/Components,如下图Figure 6-11所示:

 

Figure 6-11.Selecting Components And Controls.

Visual C++ 收集所有的你的系统中的组件和控件信息展示在Components 和Controls 陈列对话框,如图Figure 6-12所示.

Figure 6-12.Components And Controls Gallery dialog box.

双击 Registered ActiveX Controls , 定位到并选择Microsoft Web Browser, 点击插入按钮,提示你是否想加组件, 点击 OK。wizard 显示确认对话框如 Figure 6-13.

Figure 6-13.Confirm Classes dialog box.

缺省情况下, CWebBrowser2 将被选择。CWebBrowser2 类是VC为你创建的WebBrowser 控件的包装类。 因为该类特定实现于MFC, 所以你仅可在MFC项目中使用。点击OK 按钮增加CWebBrowser2 到项目中. 然后关闭陈列对话框

包含 WebBrowser2.h 在你的view 类的头文件—MfcWebHostView.h中:

#include "WebBrowser2.h"

创建private 或者 protected 数据成员,命名为m_webBrowser. 声明如下:

protected:
   CWebBrowser2 m_webBrowser;

WM_CREATE 消息建立消息处理句柄. 在此事件处理中, 使用m_webBrowserCreate 方法加入创建一个webbrowser控件的新实例。 (Create 方法是包装类为你创建的.) OnCreate 消息处理代码看起来如下:

int CMfcWebHostView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
   if (CView::OnCreate(lpCreateStruct) == -1)
      return -1;
   
   // Create WebBrowser control
   //
   if (!m_webBrowser.Create(NULL, WS_CHILD|WS_VISIBLE,
                     CRect(), this, NULL))
   {
     return -1;
   }
   
   return 0;
}

现在为WM_SIZE 消息创建消息处理. 当应用程序改变大小时候修改WebBrowser 控件的大小。如果你不包含以下代码,你的View窗口永远也不会显示webbrowser控件。 此处展示该消息处理代码:

void CMfcWebHostView::OnSize(UINT nType, int cx, int cy) 
{
   CView::OnSize(nType, cx, cy);
   
   
   // Resize WebBrowser control
   //
   m_webBrowser.MoveWindow( 0, 0, cx, cy );
   m_webBrowser.UpdateWindow();
}

覆盖OnInitialUpdate m基类的代码在应用程序首次创建时导航到用户主页。此处展示OnInitialUpdate 消息处理代码:

void CMfcWebHostView::OnInitialUpdate() 
{
   CView::OnInitialUpdate();
   
   // Navigate to the user's home page.
   //
   m_webBrowser.GoHome();
}   

编译且运行程序.将导航到用户的主页,如Figure 6-14.

Figure 6-14.MfcWebHost application.

现在加入一些访问internet的功能。 增加 Navigate包含一些Go Back, Go Forward, Go Home, Go Search, Go To A Web Page、Stop等子菜单, 你的菜单看起来类似Figure 6-15.

Figure 6-15.Navigate menu.

现在你可以加入快捷健到你的菜单,例如Alt-Left 组合键给Go Back. 你可以使用你习惯的组合健

 

为每一个菜单项建立实现句柄。增加菜单消息处理句柄代码是较为容易的。举例,GoBack 方法实现Go Back 菜单项代码看起来如下:

void CMfcWebHostView::OnNavigateGoBack() 
{
   m_webBrowser.GoBack();
}
 
void CMfcWebHostView::OnNavigateGoForward() 
{
   m_webBrowser.GoForward();
}
 
void CMfcWebHostView::OnNavigateGoHome() 
{
   m_webBrowser.GoHome();
}
 
void CMfcWebHostView::OnNavigateGoSearch() 
{
   m_webBrowser.GoSearch();
}
 
void CMfcWebHostView::OnNavigateStop() 
{
   m_webBrowser.Stop();
}

如上面提到,如果history列表不存在前项或者后项而用户点击Go Back 或者 Go Forward ,将会发生错误.

 

Go To A Web Page 菜单项是特别情形. 对此菜单项, 一个对话框将显示询问用户想去的URL. (见 Figure 6-16.)

Figure 6-16.Enter A URL For Navigation dialog box.

另外的选择,你可在工具条建立编辑框用于导航. 在本例子中, 我选择了对话框。当建立对话框,你可以使用ClassWizard 建立新的对话框类.命名为CAddressDlg.对话框类应当包含名为m_strAddress CString ,它将控制用户输入的地址。如果你的ClassWizard 创建此类成员,该成员将会是public的. 改变数据成员为protected, 且建立如下的访问存取方法:

public:
   const CString& GetAddress() const { return m_strAddress; }
 
protected:
   CString m_strAddress;

现在建立Go To A Web Page 菜单项的消息处理句柄.该菜单句柄将建立显示CAddressDlg 对话框. (确信CAddressDlg 的头文件包含在 MfcWebHostView.cpp中.) 在用户输入URL 且点击OK后, 应用程序将利用webbrowser的Navigate方法导航到URL 。 代码如下:

void CMfcWebHostView::OnNavigateGoToAWebPage() 
{
   CAddressDlg dlgAddr;
   
   // Show the dialog box. If the user clicks OK,
   // make sure a URL was entered. If one was entered,
   // navigate to that URL by using the Navigate method.
   //
   if (dlgAddr.DoModal() == IDOK)
   {
     CString strAddress = dlgAddr.GetAddress();
 
     if (!strAddress.IsEmpty())
     {
       COleVariant vtEmpty;
 
       m_webBrowser.Navigate(strAddress, &vtEmpty,
                        &vtEmpty, &vtEmpty, &vtEmpty);
     }
   }
}

在以上代码中, 一个CAddressDlg 类的实例被创建。DoModal 用于显示对话框。如果用户点击OK 按钮,应用程序应当检查 URL是否合法。

 

 

 

使用 ATL

在过去,寄宿WebBrowser控件的ATL应用程序项比较标准的C++没有任何优势。但是现在,新的ATL3的ActiveX控件容器类允许你较容易创建宿主WebBrowser 控件的。

因为实现ATL用户界面特征如菜单和工具条还是使用Win32实现,本例我将展示其本质上灵活的一面。我只想展示如何使用新的容器类寄宿webbrowser控件。我不准备实现任何用户界面。本例子仅仅实现一个容纳WebBrowser的框架。

用ATL建立一个WebBrowser 宿主应用程序, 启动0 Visual C++, 执行以下及个步骤:

1.    新建.

2.       选择ATL COM AppWizard, 输入AtlWebHost 作为工程名

3.       点击 OK, 选择 Executable (EXE),完成。

4.       在新建工程信息对话框点击OK,  wizard 将建立ATL可执行文件工程的基本代码。

5.       向工程中加入宿主WebBrowser 的控件A。具体操作,在ClassView面板中右击  AtlWebHost 类

6.       从上下文菜单中选择New ATL Object. ATL Object Wizard 对话框将显示

7.       从面板中选择HTML Control,如 Figure 6-17所示:

Figure 6-17.ATL Object Wizard with HTML Control selected.

8.       点击Next,为控件输入一个短小名,例如AtlWbHost.。wizard 自动填写其它部分

9.       保持推荐值直到OK.

wizard 将建立CAtlWbHost 类, 包含样本代码寄宿WebBrowser 控件.实例化WebBrowser控件的关键代码在OnCreate 方法的实现代码中, 当 WM_CREATE消息被发送到关联此类的windows窗体时被调用。wizard 为 OnCreate 插入的代码如下:

LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, 
                 BOOL& /*bHandled*/)
{
   CAxWindow wnd(m_hWnd);
   HRESULT hr = wnd.CreateControl(IDH_ATLWBHOST);
   if (SUCCEEDED(hr))
      hr = wnd.SetExternalDispatch(static_cast<IAtlWbHostUI*>(this));
   if (SUCCEEDED(hr))
      hr = wnd.QueryControl(IID_IWebBrowser2, (void**)&m_spBrowser);
   return SUCCEEDED(hr) ? 0 : -1;
}

在这段代码中, 类型微CAxWindow 的windows对象被首先创建,该对象提供ActiveX 控件容器支持. 下一步, 用CAxWindow 类的CreateControl方法创建WebBrowser控件。注意传递给CreateControl 的是HTML页的资源ID,所以当WebBrowser控件被创建时候HTML页被显示。然后SetExternalDispatch 方法被调用。该方法实现于IDocHostUIHandler接口。

如果任何一个步骤都没有错误发生, 代码将用CAxWindowQueryControl 方法查询IWebBrowser2 接口.如果一切顺利, QueryControl 返回IWebBrowser2 接口的指针,存储于m_spBrowser成员变量. 缺省情况下, wizard 将其标记为public.

提醒


作为我所认为的良好的面向对象编程习惯, 我习惯改变m_spBrowser 成员为  protected . 当然你可以决定是否也如此做

如果现在就编译执行代码, 什么事情也不会发生。你必须加入创建和显示窗体的代码。要实现,你必须首先在CAtlWbHost 类中实现Run方法. (该名字无关痛痒。你可选择任何你感兴趣的名字.) 示例代码如下:

STDMETHODIMP Run()
{
   //
   // Create and show the window.
   //
   RECT rcClient = { CW_USEDEFAULT, 0, 0, 0 };
 
   if (Create(GetDesktopWindow(), rcClient, _T("ATL Browser"),
              WS_VISIBLE | WS_OVERLAPPEDWINDOW, 0, (UINT)NULL) == NULL)
   {
     return E_FAIL;
   }
 
   ShowWindow(SW_SHOWNORMAL);
   return S_OK;
}

Create 方法被调用,  WM_CREATE 消息发送到窗口; 因此, OnCreate 方法被调用,OnCreate中包含之前讨论的寄宿WebBrowser 控件的代码。

提醒


Create 方法属于CWindowImpl 类,而该类是CcomControl的基础类。. CAtlWbHost 派生于CComControl, 这意味着你不如直接调用CWindowImpl 的方法。

下一步必须调用Run 方法以创建和显示窗体。 此调用将被生成于AtlWebHost.cpp 文件直接存在于你的应用程序消息循环之上。但在Run被调用之前, CAtlWbHost 类的实例必须被创建。你不可以简单的象其他C++对象一样简单采用New操作符创建一个实例 。你必须采用CComObject 的CreateInstance方法建立该类的实例。 在你的应用程序的消息泵处(CAtlWebHost.cpp中), 插入如下代码实例化CAtlWbHost 类, 然后调用Run 方法:

CComObject<CAtlWbHost>* pWbHost;
HRESULT hr = CComObject<CAtlWbHost>::CreateInstance(&pWbHost);
 
if (SUCCEEDED(hr))
   pWbHost->Run();
 
// Message pump
MSG msg;
while (GetMessage(&msg, 0, 0, 0))
   DispatchMessage(&msg);

如果现在编译执行, 它将可以工作了。它装入WebBrowser 控件, 导航到wizard automatically 插入到你的应用程序的HTML资源页. 你可以导航到某些有实际意义的地方. 你可在OnCreate 函数中调用GoHome 。 在你加入调用 GoHome 之后, OnCreate 函数看起来如下:

LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, 
          LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
   CAxWindow wnd(m_hWnd);
   HRESULT hr = wnd.CreateControl(IDH_ATLWBHOST);
   if (SUCCEEDED(hr))
      hr = wnd.SetExternalDispatch(static_cast<IAtlWbHostUI*>(this));
   if (SUCCEEDED(hr))
      hr = wnd.QueryControl(IID_IWebBrowser2, (void**)&m_spBrowser);
 
   if (SUCCEEDED(hr))  
      m_spBrowser->GoHome();
 
   return SUCCEEDED(hr) ? 0 : -1;
}

当你编译执行,你的应用程序看起来类似图Figure 6-18.

Figure 6-18.AtlWebHost application.

打印 Web 页

所有打印只需要调用传递OLECMDID_PRINTExecWB 方法。

MfcWebHost 要支持打印, 应当建立一个ID_FILE_PRINT菜单的菜单句柄,在菜单句柄中调webbrowser的ExecWB ,以及传递OLECMDID_PRINT. 你也可打印前提醒用户, 仅需要我们多做少量代码. 尽管如此,如果你想知道用户是否按下OK按钮或者取消按钮, 检查ExecWB的返回值。针对打印命令, 如果用户点几OK以初始化打印, ExecWB 将返回S_OK。 如果用户点击取消,ExecWB 将返回 S_OK以外的值. (说 " S_OK以外的值" 是因为取消按钮的返回值不具有典型代表性) 以下代码为当用户选择文件菜单中打印命令时的调用情况。

void CMfcWebHostView::OnFilePrint() 
{
   m_webBrowser.ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_PROMPTUSER,
                       NULL, NULL);
}

作为打印的附加功能, WebBrowser control 试图提供打印页设置功能.采用打印页设置, 用户可以改变例如页头页脚等设置。要实现业设置功能,代码调用ExecWB 并且传递 OLECMDID_PAGESETUP. 代码如下:

void CMfcWebHostView::OnFilePageSetup() 
{
   m_webBrowser.ExecWB(OLECMDID_PAGESETUP, OLECMDEXECOPT_PROMPTUSER,
                       NULL, NULL);
}

使用 Visual C++

Vc中创建浏览器应用程序毕vb中稍微困难。如果白手起家创建米的宿应用程序 (无MFC 或 ATL), 需要实现大量的COM接口来寄宿WebBrowser 控件。你必须还要利用COM的APICoCreateInstance  创建WebBrowser 控件的实例,指定 CLSID_WebBrowser a作为你想创建的对象的CLSID 。并且还要 将控件"site"于你的应用程序.

因为建立ActiveX 控件容器费本文讨论主题,所以着重讨论简单途径寄宿webbrowser控件。

使用 MFC

你可以创建3种类型的MFC应用程序: 单文档接口 (SDI), 多文档接口 (MDI), 以及基于对话框的应用程序。因为对话框MFC类似VB,所以本处将讨论采用SDI来寄宿webbrowser控件。一旦你知道如何采用SDI 应用来寄宿webbrowser控件,转为MDI 将会容易些。

在演示SDI的例子中,我将使用WebBrowser (CWebBrowser2) 包装类MFC的内置CHtmlView类将帮助你理解如何在MFC中寄宿WebBrowser 。

CHtmlView 类无需太多解释。要使用它,必须在MFC AppWizard第6步中选择你的应用程序的基类为ChtmlView, 如 Figure 6-8. 在完成 wizard后, 你的应用程序的视图类奖派生自CHtmlView. 然后你可以直接调用IWebBrowser2 接口的不同方法.

Figure 6-8.MFC AppWizard - Step 6 of 6 dialog box.

为创建MFC 单文档接口应用程序,启动Visual C++ 新建菜单。新的对话框展示如 Figure 6-9.

Figure 6-9.Visual C++ New dialog box.

在 Projects 页, 选择 MFC AppWizard (exe) 项, 输入一个项目名称 (譬如MfcWebHost之类), 点击OK. MFC AppWizardis 步骤一显示. (看 Figure 6-10.)

Figure 6-10.Step 1 of the MFC AppWizard.

选择 Single Document 。然后认可缺省选项,点击完成. 要增加WebBrowser 到你的工程,选择菜单Project/Add To Project/Components,如下图Figure 6-11所示:

 

Figure 6-11.Selecting Components And Controls.

Visual C++ 收集所有的你的系统中的组件和控件信息展示在Components 和Controls 陈列对话框,如图Figure 6-12所示.

Figure 6-12.Components And Controls Gallery dialog box.

双击 Registered ActiveX Controls , 定位到并选择Microsoft Web Browser, 点击插入按钮,提示你是否想加组件, 点击 OK。wizard 显示确认对话框如 Figure 6-13.

Figure 6-13.Confirm Classes dialog box.

缺省情况下, CWebBrowser2 将被选择。CWebBrowser2 类是VC为你创建的WebBrowser 控件的包装类。 因为该类特定实现于MFC, 所以你仅可在MFC项目中使用。点击OK 按钮增加CWebBrowser2 到项目中. 然后关闭陈列对话框

包含 WebBrowser2.h 在你的view 类的头文件—MfcWebHostView.h中:

#include "WebBrowser2.h"

创建private 或者 protected 数据成员,命名为m_webBrowser. 声明如下:

protected:
   CWebBrowser2 m_webBrowser;

WM_CREATE 消息建立消息处理句柄. 在此事件处理中, 使用m_webBrowserCreate 方法加入创建一个webbrowser控件的新实例。 (Create 方法是包装类为你创建的.) OnCreate 消息处理代码看起来如下:

int CMfcWebHostView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
   if (CView::OnCreate(lpCreateStruct) == -1)
      return -1;
   
   // Create WebBrowser control
   //
   if (!m_webBrowser.Create(NULL, WS_CHILD|WS_VISIBLE,
                     CRect(), this, NULL))
   {
     return -1;
   }
   
   return 0;
}

现在为WM_SIZE 消息创建消息处理. 当应用程序改变大小时候修改WebBrowser 控件的大小。如果你不包含以下代码,你的View窗口永远也不会显示webbrowser控件。 此处展示该消息处理代码:

void CMfcWebHostView::OnSize(UINT nType, int cx, int cy) 
{
   CView::OnSize(nType, cx, cy);
   
   
   // Resize WebBrowser control
   //
   m_webBrowser.MoveWindow( 0, 0, cx, cy );
   m_webBrowser.UpdateWindow();
}

覆盖OnInitialUpdate m基类的代码在应用程序首次创建时导航到用户主页。此处展示OnInitialUpdate 消息处理代码:

void CMfcWebHostView::OnInitialUpdate() 
{
   CView::OnInitialUpdate();
   
   // Navigate to the user's home page.
   //
   m_webBrowser.GoHome();
}   

编译且运行程序.将导航到用户的主页,如Figure 6-14.

Figure 6-14.MfcWebHost application.

现在加入一些访问internet的功能。 增加 Navigate包含一些Go Back, Go Forward, Go Home, Go Search, Go To A Web Page、Stop等子菜单, 你的菜单看起来类似Figure 6-15.

Figure 6-15.Navigate menu.

现在你可以加入快捷健到你的菜单,例如Alt-Left 组合键给Go Back. 你可以使用你习惯的组合健

 

为每一个菜单项建立实现句柄。增加菜单消息处理句柄代码是较为容易的。举例,GoBack 方法实现Go Back 菜单项代码看起来如下:

void CMfcWebHostView::OnNavigateGoBack() 
{
   m_webBrowser.GoBack();
}
 
void CMfcWebHostView::OnNavigateGoForward() 
{
   m_webBrowser.GoForward();
}
 
void CMfcWebHostView::OnNavigateGoHome() 
{
   m_webBrowser.GoHome();
}
 
void CMfcWebHostView::OnNavigateGoSearch() 
{
   m_webBrowser.GoSearch();
}
 
void CMfcWebHostView::OnNavigateStop() 
{
   m_webBrowser.Stop();
}

如上面提到,如果history列表不存在前项或者后项而用户点击Go Back 或者 Go Forward ,将会发生错误.

 

Go To A Web Page 菜单项是特别情形. 对此菜单项, 一个对话框将显示询问用户想去的URL. (见 Figure 6-16.)

Figure 6-16.Enter A URL For Navigation dialog box.

另外的选择,你可在工具条建立编辑框 用于导航. 在本例子中, 我选择了对话框。当建立对话框,你可以使用ClassWizard 建立新的对话框类.命名为CAddressDlg.对话框类应当包含名为m_strAddress CString ,它将控制用户输入的地址。如果你的ClassWizard 创建此类成员,该成员将会是public的. 改变数据成员为protected, 且建立如下的访问存取方法:

public:
   const CString& GetAddress() const { return m_strAddress; }
 
protected:
   CString m_strAddress;

现在建立Go To A Web Page 菜单项的消息处理句柄.该菜单句柄将建立显示CAddressDlg 对话框. (确信CAddressDlg 的头文件包含在 MfcWebHostView.cpp中.) 在用户输入URL 且点击OK后, 应用程序将利用webbrowser的Navigate方法导航到URL 。 代码如下:

void CMfcWebHostView::OnNavigateGoToAWebPage() 
{
   CAddressDlg dlgAddr;
   
   // Show the dialog box. If the user clicks OK,
   // make sure a URL was entered. If one was entered,
   // navigate to that URL by using the Navigate method.
   //
   if (dlgAddr.DoModal() == IDOK)
   {
     CString strAddress = dlgAddr.GetAddress();
 
     if (!strAddress.IsEmpty())
     {
       COleVariant vtEmpty;
 
       m_webBrowser.Navigate(strAddress, &vtEmpty,
                        &vtEmpty, &vtEmpty, &vtEmpty);
     }
   }
}

在以上代码中, 一个CAddressDlg 类的实例被创建。DoModal 用于显示对话框。如果用户点击OK 按钮,应用程序应当检查 URL是否合法。

 

 

 

使用 ATL

在过去,寄宿WebBrowser控件的ATL应用程序项比较标准的C++没有任何优势。但是现在,新的ATL3的ActiveX控件容器类允许你较容易创建宿主WebBrowser 控件的。

因为实现ATL用户界面特征如菜单和工具条还是使用Win32实现,本例我将展示其本质上灵活的一面。我只想展示如何使用新的容器类寄宿webbrowser控件。我不准备实现任何用户界面。本例子仅仅实现一个容纳WebBrowser的框架。

用ATL建立一个WebBrowser 宿主应用程序, 启动0 Visual C++, 执行以下及个步骤:

1.    新建.

2.       选择ATL COM AppWizard, 输入AtlWebHost 作为工程名

3.       点击 OK, 选择 Executable (EXE),完成。

4.       在新建工程信息对话框点击OK,  wizard 将建立ATL可执行文件工程的基本代码。

5.       向工程中加入宿主WebBrowser 的控件A。具体操作,在ClassView面板中右击  AtlWebHost 类

6.       从上下文菜单中选择New ATL Object. ATL Object Wizard 对话框将显示

7.       从面板中选择HTML Control,如 Figure 6-17所示:

Figure 6-17.ATL Object Wizard with HTML Control selected.

8.       点击Next,为控件输入一个短小名,例如AtlWbHost.。wizard 自动填写其它部分

9.       保持推荐值直到OK.

wizard 将建立CAtlWbHost 类, 包含样本代码寄宿WebBrowser 控件.实例化WebBrowser控件的关键代码在OnCreate 方法的实现代码中, 当 WM_CREATE消息被发送到关联此类的windows窗体时被调用。wizard 为 OnCreate 插入的代码如下:

LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, 
                 BOOL& /*bHandled*/)
{
   CAxWindow wnd(m_hWnd);
   HRESULT hr = wnd.CreateControl(IDH_ATLWBHOST);
   if (SUCCEEDED(hr))
      hr = wnd.SetExternalDispatch(static_cast<IAtlWbHostUI*>(this));
   if (SUCCEEDED(hr))
      hr = wnd.QueryControl(IID_IWebBrowser2, (void**)&m_spBrowser);
   return SUCCEEDED(hr) ? 0 : -1;
}

在这段代码中, 类型微CAxWindow 的windows对象被首先创建,该对象提供ActiveX 控件容器支持. 下一步, 用CAxWindow 类的CreateControl方法创建WebBrowser控件。注意传递给CreateControl 的是HTML页的资源ID,所以当WebBrowser控件被创建时候HTML页被显示。然后SetExternalDispatch 方法被调用。该方法实现于IDocHostUIHandler接口。

如果任何一个步骤都没有错误发生, 代码将用CAxWindowQueryControl 方法查询IWebBrowser2 接口.如果一切顺利, QueryControl 返回IWebBrowser2 接口的指针,存储于m_spBrowser成员变量. 缺省情况下, wizard 将其标记为public.

提醒


作为我所认为的良好的面向对象编程习惯, 我习惯改变m_spBrowser 成员为  protected . 当然你可以决定是否也如此做

如果现在就编译执行代码, 什么事情也不会发生。你必须加入创建和显示窗体的代码。要实现,你必须首先在CAtlWbHost 类中实现Run方法. (该名字无关痛痒。 你可选择任何你感兴趣的名字.) 示例代码如下:

STDMETHODIMP Run()
{
   //
   // Create and show the window.
   //
   RECT rcClient = { CW_USEDEFAULT, 0, 0, 0 };
 
   if (Create(GetDesktopWindow(), rcClient, _T("ATL Browser"),
              WS_VISIBLE | WS_OVERLAPPEDWINDOW, 0, (UINT)NULL) == NULL)
   {
     return E_FAIL;
   }
 
   ShowWindow(SW_SHOWNORMAL);
   return S_OK;
}

Create 方法被调用,  WM_CREATE 消息发送到窗口; 因此, OnCreate 方法被调用,OnCreate中包含之前讨论的寄宿WebBrowser 控件的代码。

提醒


Create 方法属于CWindowImpl 类,而该类是CcomControl的基础类。. CAtlWbHost 派生于CComControl, 这意味着你不如直接调用CWindowImpl 的方法。

下一步必须调用Run 方法以创建和显示窗体。 此调用将被生成于AtlWebHost.cpp 文件直接存在于你的应用程序消息循环之上。但在Run被调用之前, CAtlWbHost 类的实例必须被创建。你不可以简单的象其他C++对象一样简单采用New操作符创建一个实例 。你必须采用CComObject 的CreateInstance方法建立该类的实例。 在你的应用程序的消息泵处(CAtlWebHost.cpp中), 插入如下代码实例化CAtlWbHost 类, 然后调用Run 方法:

CComObject<CAtlWbHost>* pWbHost;
HRESULT hr = CComObject<CAtlWbHost>::CreateInstance(&pWbHost);
 
if (SUCCEEDED(hr))
   pWbHost->Run();
 
// Message pump
MSG msg;
while (GetMessage(&msg, 0, 0, 0))
   DispatchMessage(&msg);

如果现在编译执行, 它将可以工作了。它装入WebBrowser 控件, 导航到wizard automatically 插入到你的应用程序的HTML资源页. 你可以导航到某些有实际意义的地方. 你可在OnCreate 函数中调用GoHome 。 在你加入调用 GoHome 之后, OnCreate 函数看起来如下:

LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, 
          LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
   CAxWindow wnd(m_hWnd);
   HRESULT hr = wnd.CreateControl(IDH_ATLWBHOST);
   if (SUCCEEDED(hr))
      hr = wnd.SetExternalDispatch(static_cast<IAtlWbHostUI*>(this));
   if (SUCCEEDED(hr))
      hr = wnd.QueryControl(IID_IWebBrowser2, (void**)&m_spBrowser);
 
   if (SUCCEEDED(hr))  
      m_spBrowser->GoHome();
 
   return SUCCEEDED(hr) ? 0 : -1;
}

当你编译执行,你的应用程序看起来类似图Figure 6-18.

Figure 6-18.AtlWebHost application.

打印 Web 页

所有打印只需要调用传递OLECMDID_PRINTExecWB 方法。

MfcWebHost 要支持打印, 应当建立一个ID_FILE_PRINT菜单的菜单句柄,在菜单句柄中调webbrowser的ExecWB ,以及传递OLECMDID_PRINT. 你也可打印前提醒用户, 仅需要我们多做少量代码. 尽管如此,如果你想知道用户是否按下OK按钮或者取消按钮, 检查ExecWB的返回值。针对打印命令, 如果用户点几OK以初始化打印, ExecWB 将返回S_OK。 如果用户点击取消,ExecWB 将返回 S_OK以外的值. (说 " S_OK以外的值" 是因为取消按钮的返回值不具有典型代表性) 以下代码为当用户选择文件菜单中打印命令时的调用情况。

void CMfcWebHostView::OnFilePrint() 
{
   m_webBrowser.ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_PROMPTUSER,
                       NULL, NULL);
}

作为打印的附加功能, WebBrowser control 试图提供打印页设置功能.采用打印页设置, 用户可以改变例如页头页脚等设置。要实现业设置功能,代码调用ExecWB 并且传递 OLECMDID_PAGESETUP. 代码如下:

void CMfcWebHostView::OnFilePageSetup() 
{
   m_webBrowser.ExecWB(OLECMDID_PAGESETUP, OLECMDEXECOPT_PROMPTUSER,
                       NULL, NULL);
}

.

 

(五)

自动化 Internet Explorer

自动化打开了开发基于web应用的世界。 它允许你使用VB或者VC定制成熟的应用。自动化的好处:通过属性和方法可以改变IE的外观;你可以提供诸如导航条等用户接口以便控制用户的导航。

自动化IE很容易。你建立一个简单的应用启动一个IE实例,然后使用控制webbrowser的途径- IWebBrowser2接口来控制IE实例。

提醒


术语自动化(automation)真实的含义是通过自动化接口-- IDispatch.控制一个COM对象。但是在此是指控制IE的技术,你不需要直接通过IDispatch

使用VB

前面已经介绍了如何五分钟在VB中使用webbrowser来创建全功能的浏览器应用. 你也可以大致使用此时间用VB自动化IE。让我们开始。

启动一个Standard EXE 工程,选择References 菜单项. 引用对话框展开如Figure 6-19:

Figure 6-19.References dialog box.

滚动下拉,选中 Microsoft Internet Controls 检查框,点击OK 。加入一个命令按钮到窗体,命名为btnStart, 修改标题为Start IE5. 然后双击加入click事件代码。

当用户点击Start IE5 按钮, 你想应用程序启动一个Internet Explorer 5实例. 先建立一个类型为InternetExplorer 的全局变量. 命名为InternetExplorer1.

现在, 在btnStart的Click 事件中, 加入如果上一个实例没有创建就创建新IE实例的代码。你可以使用CreateObject 或者Vb的New 关键字.如下:

Set InternetExplorer1 = New InternetExplorer

该代码创建一个新实例,但是实例是隐藏的,要显示该实例,设定Visible属性为 True, 如下:

InternetExplorer1.Visible = True

现在你需要导航到某个web页,你可以如下调用InternetExplorer 对象的Navigate方法, 如下:

InternetExplorer1.Navigate "http://www.microsoft.com/"

至此,整个Vb的自动化IE的源代码看起来如下:

Option Explicit
Dim InternetExplorer1 As InternetExplorer
 
Private Sub btnStart_Click()
   ' Only create a new instance of Internet Explorer
   ' if one hasn't already been created.
   '
   If Not InternetExplorer1 Is Nothing Then
      Exit Sub
   End If
 
   Set InternetExplorer1 = New InternetExplorer
   
   ' Make Internet Explorer visible and navigate
   ' to Microsoft's home page.
   '
   InternetExplorer1.Visible = True
   InternetExplorer1.Navigate "http://www.microsoft.com/"
End Sub
 
Private Sub Form_Load()
   Set InternetExplorer1 = Nothing
End Sub

运行应用程序看到IE启动了! 新的IE实例将被启动导航到MS的主页。者不太困难,是不是?现在让我们加入一些实在的较酷的特征允许你控制你自己创建的IE实例。

首先保存工程为 VbAutoIE.vbp, 且保存你的表单 VbAutoIE.frm. 然后加入一些控制到你的表单,如图Figure 6-20. 这些允许你显示或者隐藏IE中不同的用户接口特征如地址栏、菜单条、状态条和工具条等。你也可以加入文字到状态条。

Figure 6-20.Visual Basic form with controls to customize the Internet Explorer user interface.

现在如下表设定每一个控件的属性如表6-8.创建4个选项组,每一个包含 一个显示和一个隐藏选项按钮如Figure 6-20.

Table 6-8. Control Properties for a Visual Basic Program Automating Internet Explorer

Control

Properties

Frame1-4

Captions = "AddressBar", "MenuBar", "StatusBar ", and "ToolBar", respectively

Hide Option Buttons

Caption = "Hide"; Index = 0; Value = False; Names =optAddrBar, optMenuBar, optStatusBar, and optToolBar, respectively

Show Option Buttons

Caption = "Show"; Index = 1; Value = True; Names = optAddrBar, optMenuBar, optStatusBar, and optToolBar, respectively

Label

Caption = "Status Text"

TextBox

Name = txtStatusText. Remove the default text for the Text property

CommandButton

Caption = "Change"; Name = btnChange

加入控制InternetExplorer 对象的代码控制浏览器的用户接口。看看清单6-1

Listing 6-1.

VbAutoIE.bas

Option Explicit
Dim InternetExplorer1 As InternetExplorer
Const HideBar = 0
Const ShowBar = 1
Private Sub btnChange_Click()
   On Error Resume Next
   InternetExplorer1.StatusText = txtStatusText.Text
End Sub
 
Private Sub btnStart_Click()
   ' Only create a new instance of Internet Explorer
   ' if one hasn't already been created.
   '
   If Not InternetExplorer1 Is Nothing Then
      Exit Sub
   End If
   
   Set InternetExplorer1 = New InternetExplorer
   
   ' Set the user interface features to match the
   ' entries specified by the user.
   '
   If optAddrBar(ShowBar).Value = True Then
      InternetExplorer1.AddressBar = True
   Else
      InternetExplorer1.AddressBar = False
   End If
   
   If optMenuBar(ShowBar).Value = True Then
      InternetExplorer1.MenuBar = True
   Else
      InternetExplorer1.MenuBar = False
   End If
   
   If optToolBar(ShowBar).Value = True Then
      InternetExplorer1.ToolBar = True
   Else
      InternetExplorer1.ToolBar = False
   End If
 
   If optStatusBar(ShowBar).Value = True Then
      InternetExplorer1.StatusBar = True
   Else
      InternetExplorer1.StatusBar = False
   End If
   
   ' Make Internet Explorer visible and navigate
   ' to Microsoft's home page.
   '
   InternetExplorer1.Visible = True
   InternetExplorer1.Navigate "http://www.microsoft.com/"
End Sub
 
Private Sub Form_Load()
   Set InternetExplorer1 = Nothing
End Sub
 
Private Sub Form_Unload(Cancel As Integer)
   On Error Resume Next
   InternetExplorer1.Quit
End Sub
 
Private Sub optAddrBar_Click(Index As Integer)
   On Error Resume Next
   InternetExplorer1.AddressBar = CBool(Index)
End Sub
 
Private Sub optMenuBar_Click(Index As Integer)
   On Error Resume Next
   InternetExplorer1.MenuBar = CBool(Index)
End Sub
 
Private Sub optStatusBar_Click(Index As Integer)
   On Error Resume Next
   InternetExplorer1.StatusBar = CBool(Index)
End Sub
 
Private Sub optToolBar_Click(Index As Integer)
   On Error Resume Next
   InternetExplorer1.ToolBar = Index
End Sub

在清单6-1, 当表单被装载,  InternetExplorer1对象设定为Nothing.当Start IE5 按钮被点击, 我们检查确信没有上一个实例启动,如果启动了我们直接返回。

如果上一实例没有启动,我们采用关键字New 创建一个新实例。然后我们检查选项组的状态.我们依据选项当前值进行IS属性的设置。然后设置Visible 属性为 True. 最后我们使用Navigate方法导航到MS的主页.

 

(六)

使用VC++和COM API

尽管使用VC不像Vb中那么容易自动化IE,但是也不太难,尤其是你理解了CON和COM API。无论你使用MFC, ATL, 或者标准 C++自动化IE,方法都是一样—你使用COM API来实现.

VC++中创建一个Internet Explorer实例包括要调用COM APICoCreateInstance ,指定第一个参数为 CLSID_InternetExplorer 。创建IE自动化实例不像创建包含webbrowser的Activex控件困难。你不需要实现容器或者site对象的必要接口。

来看看如何容易的使用CoCreateInstance创建IE实例吧。启动Visual C++, 新建MFC AppWizard (exe) 应用程序命名为 MfcAutoIE.选择dialog-based option, 认可其他缺省选项.

现在加入如VbAutoIE中的控件到表单.对话框看起来如 6-21. 分派ID到对话框的各个控件。确信控件TAB次序如 Figure 6-22. (tab 次序影响到radio按钮的工作)

Figure 6-21.MfcAutoIE dialog.

Figure 6-22.MfcAutoIE dialog tab order.

现在用右键菜单设置每一个隐藏radio按钮的Group,属性设置如表 6-9.

Table 6-9. Member Variables for MfcAutoIE Dialog Controls

Control

Type

Member Variable

Hide radio button for AddressBar

int

m_nAddressBar

Edit box

CString

m_strStatusText

Hide radio button for MenuBar

int

m_nMenuBar

Hide radio button for StatusBar

int

m_nStatusBar

Hide radio button for ToolBar

int

m_nToolBar

我们使用ClassWizard 为表 6-9 中的控件建立变量时,他们自动加入到CMfcAutoIEDlg. 成员变量设置为-1.

编译MfcAutoIE Example之前我们设置编译的Directory次序 属性

编译之前,你需要处理一些重要任务:

1.    确信你已经从MSDN中下载IE5的头文件和库文件。

2. 在Tools/Options 菜单的Directories页, 确信lib路径中包含Internet Explorer 5 和 Windows 2000库文件.载列表中。

3. 配置Include: 略

 

现在我们加入代码使之工作。首先你应当包含ExDisp.h到你的对话框头文件—MfcAutoIEDlg.h. ExDisp.h 是包含了WebBrowser接口和类ID的头文件.确信你已经从MSDN下载了最新的版本。

建立一个private 或者protected 的数据类型,指向IWebBrowser2 的指针,  命名为 m_pInetExplorer. 如下

protected:
   IWebBrowser2* m_pInetExplorer;

现在在构造函数中初始化m_pInetExplorerNULL 。你必须也初始化COM. 放置一个CoInitialize 的COM API调用在构造函数。构造函数看起来应当如下:

CMfcAutoIEDlg::CMfcAutoIEDlg(CWnd* pParent /*=NULL*/)
   : CDialog(CMfcAutoIEDlg::IDD, pParent),
     m_pInetExplorer(NULL)
{
   //{{AFX_DATA_INIT(CMfcAutoIEDlg)
   m_strStatusText = _T("");
   m_nAddressBar = -1;
   m_nMenuBar = -1;
   m_nStatusBar = -1;
   m_nToolBar = -1;
   //}}AFX_DATA_INIT
   //Note that LoadIcon does not require a subsequent DestroyIcon
   //in Win32.
   m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
 
   // Initialize COM
   CoInitialize(NULL);
}

建立一个析构函数,以便调用CoUninitialize API 函数用以反初始化 COM. 此处的析构函数:

CMfcAutoIEDlg::~CMfcAutoIEDlg()
{
   // Uninitialize COM.
   //
   CoUninitialize();
}

在预备工作之后, 让我们开始实现具体的控制。首先为Start IE5 按钮建立一个消息循环. 通常,你可以使用ClassWizard建立消息循环处理句柄.在此消息句柄, 采用 CoCreateInstance API 建立一个IE实例。 此处初建的Internet Explorer实例初始化为隐藏, 所以你必须使用Visible属性使他可见。为了导航到用户的主页,使用GoHome 方法。此处为消息处理句柄代码:

void CMfcAutoIEDlg::OnStartIE5() 
{
   // If an instance of Internet Explorer has
   // not already been created, create one.
   // This instance will initially be hidden,
   // so make it visible by using the Visible
   // property. Also, navigate to the user's
   // home page by using the GoHome method.
   //
   if (m_pInetExplorer)
      MessageBox
         (_T("Only one instance of Internet Explorer is allowed."));
   else
   {
      HRESULT hr;
      hr = CoCreateInstance(CLSID_InternetExplorer, NULL, CLSCTX_SERVER,
                           IID_IWebBrowser2, (LPVOID*)&m_pInetExplorer);
 
      if (SUCCEEDED(hr))
      {
         // Set the radio buttons to their correct values.
         SetRadioButtons();
 
         m_pInetExplorer->put_Visible(VARIANT_TRUE);
         m_pInetExplorer->GoHome();
      }
   }
}

在此代码中,我们首先检查是否IE的上一个实例已经建立。如果已经建立,将会显示一个错误消息框。(你仅仅能够启动一个IE的实例,随后你将可以看到如何检测IE窗口被关闭以便重置IE的数据成员。)如果一个IE的实例还未创建,CoCreateInstanceCLSID_InternetExplorer 作为第一个参数被调用. (此类 ID 定义于ExDisp.h ,为Internet Explorer的唯一标示符.)

第二个参数我们设定为NULL ,因为我们不希望此COM对象被聚合(be aggregated). 我们希望IE运行在一个单独的进程空间,所以我们指定第三个参数为特殊值CLSCTX_SERVER .我们利用第四个参数指示哪一个接口我们将通过CoCreateInstance 调用创建一个COM对象后被创建。在此例中,我们将一直希望获得IWebBrowser2, 所以我们指定IID_IWebBrowser2 为第四个参数值。最后我们传送存储接口指针的变量. 对于此参数,你必须采用void类型传递一个指针的地址 (确信你传送的指针指向一个接口类型。)

如果一个IE实例成功创建,名为SetRadioButtons的成员变量被创建。这是一个protected 成员函数,检查当前地址栏、菜单条、工具条和状态条等的状态,用来设置radio 按钮组的状态。.为检查每一个的状态我们简单的获取属性关联的当前值。

C++ 应用中使用COM 没有包装类, 属性被引用为使用get_ and put_ 方法.为检取每一个用户问题的状态,我们简单的调用每一个关联的(UI)项目的get_ 方法 .除了get_ToolBar外其他get_方法返回指向VARIANT_BOOL 数据类型,返回值指示用户接口是否可见或者隐藏。如果用户接口元素可见,将返回VARIANT_TRUE。如果该项目是隐藏的,将返回VARIANT_FALSE 。这些值区别于TRUE FALSE 的用法.

提醒


当在Visual C++涉及 VARIANT_BOOL , 你必须使用VARIANT_TRUE 或者 VARIANT_FALSE代替 TRUE or FALSE. VARIANT_TRUE定义值 0xffff, 而TRUE 定义为 1. 如果你比较 VARIANT_TRUE TRUE, 你将发现在Vb中不匹配,你可以在涉及到VARIANT_BOOL. 使用true和false是由于VB替你实现了转换。

get_ToolBar 方法不同于其他属性,因为它指向一个整型指针,所以我们如果发现返回非0,工具条可见。如果整型值是0,工具条将隐藏:

 

提醒


如果一个属性只读, put_ 将不会出现。同样,如果一个属性只写,将不会出现get_ 方法。

此处为 SetRadioButtons 方法的代码:

void CMfcAutoIEDlg::SetRadioButtons()
{
   VARIANT_BOOL vtBool = VARIANT_TRUE;
 
   // Get the current state of the AddressBar.
   //
   m_pInetExplorer->get_AddressBar(&vtBool);
   m_nAddressBar = (vtBool == VARIANT_TRUE) ? 1 : 0;
 
   // Get the current state of the MenuBar.
   //
   m_pInetExplorer->get_MenuBar(&vtBool);
   m_nMenuBar = (vtBool == VARIANT_TRUE) ? 1 : 0;
 
   // Get the current state of the StatusBar.
   //
   m_pInetExplorer->get_StatusBar(&vtBool);
   m_nStatusBar = (vtBool == VARIANT_TRUE) ? 1 : 0;
 
   // Get the current state of the ToolBar.
   // Unlike the other get methods, get_ToolBar
   // takes a pointer to an integer.
   //
   m_pInetExplorer->get_ToolBar(&m_nToolBar);
 
   UpdateData(FALSE);  // Initializes dialog box with changed values.
}

:

void CMfcAutoIEDlg::OnAddrBarShowHide() 
{
   UpdateData(TRUE);
 
   if (m_pInetExplorer)
   {
      VARIANT_BOOL vtShow = 
         m_nAddressBar ? VARIANT_TRUE : VARIANT_FALSE;
 
      m_pInetExplorer->put_AddressBar(vtShow);
   }
}
 
void CMfcAutoIEDlg::OnMenuBarShowHide() 
{
   UpdateData(TRUE);
 
   if (m_pInetExplorer)
   {
      VARIANT_BOOL vtShow = 
         m_nMenuBar ? VARIANT_TRUE : VARIANT_FALSE;
 
      m_pInetExplorer->put_MenuBar(vtShow);
   }
}
 
void CMfcAutoIEDlg::OnStatusBarShowHide() 
{
   UpdateData(TRUE);
 
   if (m_pInetExplorer)
   {
      VARIANT_BOOL vtShow = 
         m_nStatusBar ? VARIANT_TRUE : VARIANT_FALSE;
 
      m_pInetExplorer->put_StatusBar(vtShow);
   }
}
 
void CMfcAutoIEDlg::OnToolBarShowHide() 
{
   UpdateData(TRUE);
 
   if (m_pInetExplorer)
      m_pInetExplorer->put_ToolBar(m_nToolBar);
}

注意到OnToolBarShowHide 方法中我们将m_nToolBar 数据成员传递给put_ToolBar 方法替代了VARIANT_BOOL. 我们暂时不解释为什么,就像get_ToolBar,put_ToolBar 传递一个整型而不是 VARIANT_BOOL.

现在利用ClassWizard 建立一个Change 按钮的消息循环处理句柄 。此消息的句柄将在任何时候设定set Internet Explorer 的状态条为edit 输入框内容。 代码看起来如下:

void CMfcAutoIEDlg::OnChangeStatusText() 
{
   UpdateData(TRUE);
 
   if (m_pInetExplorer)
   {
      _bstr_t bstrStatusText = m_strStatusText.AllocSysString();
      m_pInetExplorer->put_StatusText(bstrStatusText);
   }
}

确信你在第一个任务就是调用传递TRUE的 UpdateData and .这样的目的是更新对阿框中所有相关的控件, 包括m_strStatusText. 如果m_pInetExplorer 不是NULL—那意味着 Internet Explorer一个实例已经被创建—StatusText 被设定为用户输入的文字。 StatusText 同样事采用put_ 设定Visible属性。 该函数需要一个BSTR的入口参数,所以调用 AllocSysString 方法使得m_strStatusText 数据成员变量分派为BSTR 可以传递给put_StatusText.AllocSysStringCString 的方法,返回一个Cstring对象中值的 BSTR 类型。BSTR 返回自AllocSysString 存储于_bstr_t类型的变量, 一个 COM 编译器支持的Visual C++类。为了使用此类,你必须包含comdef.h 头文件,置于对话框实现文件CMfcAutoIEDlg—MfcAutoIEDlg.cpp. 该类将小心处理BSTR 离开当前范围的状况,所以你不需要调用SysFreeString的Win32 API 以释放调用put_StatusText.之后的BSTR.

至此,我们基本上完成了。为了使用户输入Enter时不关闭对话框,重写基类对话框的OnOK方法。在此方法中,简单的不做任何事。确信你没有调用基类的CDialog::OnOK 方法,否则,对话框会关闭。

现在来重写OnCancel 的基类方法, 该方法调用于用户按下ESC或者点击对话框的右键菜单的关闭或者对话框的X按钮。在OnCancel中,如果一个Internet Explorer 实例已经被创建, 在调用Quit 方法时关闭InternetExplorer 对象。Quit 甚至可以在用户默认关掉Internet Explorer. 如果出现此情形, Quit 方法将返回 RPC_S_SERVER_UNAVAILABLE, 标示Internet Explorer 不再可用.你可以简单忽略此错误消息。以下为OnCancel的代码:

void CMfcAutoIEDlg::OnCancel()
{
   if (m_pInetExplorer)
      m_pInetExplorer->Quit();
 
   CDialog::OnCancel();
}

最后, 你必须释放掉指向IE的IWebBrowser2接口指针。你可以在对话框的析构函数中处理。此处为更型后的析构函数:

 

CMfcAutoIEDlg::~CMfcAutoIEDlg()
{
   // Release the WebBrowser interface pointer
   //
   if (m_pInetExplorer)
   {
      m_pInetExplorer->Release();
      m_pInetExplorer = NULL;
   }
 
   // Uninitialize COM
   //
   CoUninitialize();
}

现在,通常我们可以编译运行对话框应用程序。启动IE实例,测试。

(七)

]加入高级功能

本节将展示如何加入高级功能到你的应用程序或者ActiveX控件.将展示如何从一个ActiveX控件中访问Internet Explorer的IWebBrowser2,以及如何从一个Web页面的frame中获得WebBrowser 对象。将会展示实现一些并非容易实现的功能.本节有一定难度.所有代码采用C++ 和COM实现,你应当有一定坚实的基础才可以完成本节的理解

从ActiveX 控件中访问Internet Explorer 的IWebBrowser2

在Activx控件中访问IWebBrowser2接口提供了用户定制浏览器的能力,虽然以此作为自己的控件功能不大光明,且你只能够在VC编写的activeX控件中访问。(尽管可以在VB写的控件中访问document和window控件,但是你不可以直接访问WebBrowser自身) .

存取  IWebBrowser2 接口经过四个步骤:

1.    在类文件中包含 ExDisp.h .

2.       调用控件的站点的IOleClientSite::GetContainer 方法,该方法返回由Internet Explorer.实现的IOleContainer 接口的指针。

3.       如果步骤2成功,用 IOleContainer 指针查询IServiceProvider 接口。.

4.       如果步骤3 成功, 调用IServiceProvider的方法QueryService 的到 IWebBrowser2 接口。

QueryService 方法携带3个参数. 第一个参数指定你想访问的服务。为得到IWebBrowser2指针, 需要指定SID_SInternetExplorer 或者SID_SWebBrowserApp 来指定要访问的服务. (现阶段,他们全部定义为IID_IWebBrowserApp.) 第二个参数指定你想接收的接口的ID。此参数你应当指定为IID_IWebBrowser2. 最后,第三个参数你需要指定哪一个变量接收返回的接口指针。

Call any method or property ofIWebBrowser2. 当完成后,确信你已经释放掉你获取的接口指针.以下为代码参数.

 
// Begin Step 1
 
#include <ExDisp.h>
#include <shlguid.h>
 
// End Step 1
 
 
CSomeClass::SomeMethod(){
 
 
// Begin Step 2
 
IOleContainer* pContainer;
 
// m_pClientSite is a pointer to IOleClientSite.
// This is the client site for your control.
//
HRESULT hr = m_pClientSite->GetContainer(&pContainer);
if (FAILED(hr))
   return hr;
 
// End Step 2
 
 
 
// Begin Step 3
 
IServiceProvider* pServiceProvider;
 
hr = pContainer->QueryInterface(IID_IServiceProvider,
                                (void**)&pServiceProvider);
pContainer->Release();
 
if (FAILED(hr))
   return hr;
 
// End Step 3
 
 
 
 
// Begin Step 4
 
IWebBrowser2* pWebBrowser;
 
hr = pServiceProvider->QueryService(SID_SWebBrowserApp,
                           IID_IWebBrowser2,
                           (void**)&pWebBrowser);
pServiceProvider->Release();
 
if (FAILED(hr))
   return hr;
 
// End Step 4
 
 
 
 
// Begin Step 5
 
// Call some IWebBrowser2 methods and/or properties.
 
// End Step 5
 
}

利用一个控件打印web页

早于5的Internet Explorer,通常用于ActiveX中 访问IWebBrowser2接口以提供打印web页面的功能。尽管ie5允许你直接在web页的script中使用 window.print, 但是建立一个提供打印web页的功能的示范还是比较好教您使用IWebBrowser2接口的入门教程.在此之前,必要知道如何使用ATL建立一个activeX控件以实现打印功能. 关于此点,我将认为您已经知道如何使用ATL创建一个控件.

启动Visual C++, 新建 ATL DLL工程. 你可将工程命名为AtlPrint. 下一步, 使用Wizard增加一个Lite Control 到你的工程。 你可以命名你的控件为PrintCtl. 为IPrintCtl接口添加一个方法Print. 之后你将在脚本中使用此方法打印当前Web page.

在实现Print 方法前,首先包含ExDisp.h 和 shlguid.h 头文件到PrintCtl.cpp 实现文件.

下一步使用之前给出的获取IWebBrowser2 接口的方法获得接口并调用ExecWB 以实现打印当前Web页. 你可以使用ATL 智能接口指针类—CComPtr以及 CComQIPtr—以实现查询接口处理引用次数等艰苦的工作.此处为智能指针示勇代码:

STDMETHODIMP CPrintCtl::Print()
{
   HRESULT hr = E_FAIL;
 
   if (m_spClientSite)
   {
      CComPtr<IOleContainer> spContainer;
   
      hr = m_spClientSite->GetContainer(&spContainer);
      ATLASSERT(SUCCEEDED(hr));
 
      if (SUCCEEDED(hr))
      {
         CComQIPtr<IServiceProvider, &IID_IServiceProvider>
            spServiceProvider(spContainer);
 
         ATLASSERT(spServiceProvider);
 
         if (!spServiceProvider)
            hr = E_FAIL;
         else
         {
            CComPtr<IWebBrowser2> spWebBrowser;
 
            hr = spServiceProvider->QueryService(SID_SInternetExplorer,
                                                 IID_IWebBrowser2,
                                                 (void**)&spWebBrowser);
            ATLASSERT(SUCCEEDED(hr));
 
            if (SUCCEEDED(hr))
            {         
               spWebBrowser->ExecWB(OLECMDID_PRINT, 
                                 OLECMDEXECOPT_PROMPTUSER,
                                 NULL, NULL);
            }
         }
      }
   }
 
   return hr;
}

现在编译ATL ActiveX control. 为测试打印功能,你必须加入一些script. Listing 6-2 展示了这部分代码:

Listing 6-2.

PrintCtl.htm

<HTML>
<HEAD>
   <TITLE>ATL 3.0 test page for object PrintCtl</TITLE>
 
   <SCRIPT LANGUAGE="VBS">
      Sub btnPrint_onclick
         PrintCtl.Print
      End Sub
   </SCRIPT>
</HEAD>
<BODY>
   <OBJECT ID="PrintCtl"
      CLASSID="CLSID:320B04E4-B55B-11D2-A9BA-444553540001">
   </OBJECT>
   <P>
   <BUTTON ID="btnPrint">Print Page</BUTTON>
</BODY>
</HTML>

当宿主WebBrowser控件的时候存取帧的IWebBrowser2 接口

如果一个Web 页包含了多帧,每一帧都包含了一个WebBrowser对象。 当宿主WebBrowser 控件, 你将可被允许从应用程序访问WebBrowser 以控制帧.此控允许你控制以在帧中导航, 刷新, 以及诸如此类。一旦你拥有了一个帧中的WebBrowser 对象的IWebrowser2 接口指针, 你可以掉用IWebrowser2 接口的任何方法和属性。

在VC++中你可以访问一个帧中的WebBrowser. VB中,你可在帧中存取document, 你可以访问WebBrowser 但是不可以访问装载于WebBrowser窗口中的HTML文档对象的 IoleContainer接口. 存取 IOleContainer 要求访问帧的 WebBrowser 对象. 本节讲述VC++d的标准技术细节,即WebBrowser 控件的宿主能访问在包含的WebBrowser控件装载的web页面的帧窗口的WebBrowser对象模型.

下面的代码展示如何访问web页面的每一个帧的WebBrowser对象以刷新每一帧的内容.其中重要的片断用于用HTMLdocument 对象的IOleContainer::EnumObjects  方法枚举页面中的embeddings(嵌入)对象. 每一个嵌入对象表现为一个控件. 利用IWebBrowser2 接口查询每一个控件对象,此代码可检测到控件是否是一个子帧.如果为获得IWebBrowser2 而成功调用QueryInterface, 其结果为帧中的WebBrowser对象的引用. (数据成员m_webBrowserCWebBrowser2类型的—MFC 包装类)

// Get the IDispatch of the document.
//
LPDISPATCH lpDisp = NULL;
lpDisp = m_webBrowser.GetDocument();
 
if (lpDisp)
{
   IOleContainer* pContainer;
 
   // Get the container.
   //
   HRESULT hr = lpDisp->QueryInterface(IID_IOleContainer,
                                       (void**)&pContainer);
   lpDisp->Release();
 
   if (FAILED(hr))
      return hr;
 
   // Get an enumerator for the frames.
   //
   IEnumUnknown* pEnumerator;
 
   hr = pContainer->EnumObjects(OLECONTF_EMBEDDINGS, &pEnumerator);
   pContainer->Release();
 
   if (FAILED(hr))
      return hr;
 
   IUnknown* pUnk;
   ULONG uFetched;
 
   // Enumerate and refresh all the frames.
   //
   for (UINT i = 0; S_OK == pEnumerator->Next(1, &pUnk, &uFetched); i++)
   {
      // QI for IWebBrowser here to see whether we have 
      // an embedded browser.
      IWebBrowser2* pWebBrowser;
 
      hr = pUnk->QueryInterface(IID_IWebBrowser2, (void**)&pWebBrowser);
      pUnk->Release();
 
      if (SUCCEEDED(hr))
      {
         // Refresh the frame.
         pWebBrowser->Refresh();
         pWebBrowser->Release();
      }
   }
 
   pEnumerator->Release();
}

请留意在代码中我们首先通过GetDocument 方法获得了文档的IDispatch对象指针, 该方法是WebBrowser 包装类的成员指针.然后我们访问文档的IOleContainer 接口. IOleContainer 接口提供了能够枚举页面中全部嵌入对象的功能. 然后我们通过调用IOleContainerEnumObjects 方法得到枚举器(EnumObjects. EnumObjects返回的 IEnumUnknown 接口的指针可以用于枚举全部嵌入(embeddings)对象. 代码的下一步, 我们遍历全部嵌入对象,查询每一个对象的IWebBrowser2 接口. 如果查询成功, 我们已经获得了帧的IWebBrowser2接口指针.我们可以调用它的任何方法和属性,在此例中,我们仅仅调用了每一个帧的Refresh.

忠告


ActiveX 控件宿主于一个html页也有类似情。也许,如果你建立一个访问ie的 WebBrowser 控件或者页面帧中的WebBrowser 的控件,不要将你的控件标记为脚本安全和初始化安全。.

调用 查找, 察看源码, 以及 Internet 选项

浏览WebBrowser 控件的方法和属性, 你可轻易见到控件提供的功能.但是3个可编程由WebBrowser控件提供的项目不容易发觉到:查找对话框, 察看源代码菜单项,以及Internet选项对话框. 如果你曾经用过ie,你无疑很熟悉他们. 查找对话框, 允许你在WEB页中查找文本,通过处理 Ctrl-F 或者选择edit菜单的Find项来调用.

察看源代码菜单项,允许你显示WEB页的html代码, 可通过选择View菜单的Source项来选择或者右击web页(在弹处菜单中)选择察看源代码. Internet 选项对话框如图6-23所示, 可通过选择Tools菜单的Internet Option项来调用.

Figure 6-23.Internet Options dialog box.

能够在你的web应用中提供以上功能的确可以带来对用户的友善性,但是调用途径缺不是通过WebBrowser的方法和属性可以实现. 实际上你应当从调用IOleCommandTargetExec方法来调用实现于WebBrowser中的以上功能.当调用Exec, 你传递名为 CGID_IWebBrowser 的GUID 作为你想调用的命令的ID.尽管ExecWB方法是IOleCommandTarget::Exec 方法的包装, 但是你不能够通过ExecWB 来调用 Find, View Source, 或者 Internet Options 对话框,以为ExecWB 不允许你指定命令组GUID. 那意味着该项技术仅能够用于VC++—你不能够直接从VB中调用。.

忠告


本代简要展示你未收入文档的命令组GUID ,那意味着可以将来改编。尽管代码已经在Internet Explorer 3.x, 4.x, 和 5中测试,,但是不保证在将来的版本中成功运行

以下步骤实现 Find, View Source, 以及Internet Options 命令:

1.    定义WebBrowser 控件的命令组GUID:

DEFINE_GUID(CGID_IWebBrowser,0xED016940L,0xBD5B,0x11cf,0xBA,
         0x4E,0x00,0xC0,0x4F,0xD7,0x08,0x16);

2.       定义用于Find, View Source, 和 Internet Options的ID:

     
     #define HTMLID_FIND 1
     #define HTMLID_VIEWSOURCE 2
     #define HTMLID_OPTIONS 3

3.       在需要的时候执行Find, View Source, 和Internet Options 命令. 举例来说,你可创建工具方法接收命令ID并调用IOleCommandTarget::Exec, 如下片断所示. (注意在MFC代码中,m_webBrowser 是 WebBrowser 控件的实例. 同样,nCmdID 是定义的ID)

HRESULT CYourView::ExecCmdTarget(DWORD nCmdID)
{
   LPDISPATCH lpDispatch = NULL;
   LPOLECOMMANDTARGET lpOleCommandTarget = NULL;
   HRESULT hr = E_FAIL;
 
   // Get the IDispatch of the document.
   //
   lpDispatch = m_webBrowser.GetDocument();
   ASSERT(lpDispatch);
 
   if (lpDispatch)
   {
      // Get a pointer for the IOleCommandTarget interface.
      //
      hr = lpDispatch->QueryInterface(IID_IOleCommandTarget,
                                      (void**)&lpOleCommandTarget);
      lpDispatch->Release();
 
      ASSERT(lpOleCommandTarget);
 
      if (SUCCEEDED(hr))
      {
         // Invoke the given command id 
         // for the WebBrowser control.
         hr = lpOleCommandTarget->Exec
              (pguidCmdGroup, nCmdID, 0, NULL, NULL);
         lpOleCommandTarget->Release();
      }
   }
 
   return hr;
}

 

 

 

 

分发WebBrowser 控件

现在你知道了如何利用WebBrowser控件和IE创建专用应用程序, 你大概感兴趣知道那些IE组件你需要打包在你的应用程序以便你的应用程序可以在系统商正常工作即使没有IE5被安装.无论你宿主 WebBrowser 控件还是自动化Internet Explorer, 你必须至少打包IE最小化安装的组件. 为理解为什么,请再一次察看图6-1展示的体系.你可以看到每一个组件是如何的依赖其他组件—而且这幅图仅仅展示了其表面.它未能展示webbrowser控件和IE多样特性的组件间的全部特征.因为由如此众多的组件调用,为了你的应用程序能够正常工作,你应当确信至少IE最小化安装于用户的操作系统.

别担心,你不需要打包每一个IE的组件.  Internet Explorer 5 安装程序允许你定制你的安装以使你打包你仅需的组件.另外, 如果你使用Internet Explorer Administration Kit (可在http://ieak.microsoft.com/找到) 建立你的安装程序, 你可以将IE默认安装,免掉多个确认安装步骤的对话框.

 

关于Cookie的思考

Cookie是什么?
Cookie产生的背景是源于年代并不久远但是却很经典的HTTP协议在互联网上的急速发展。互联网之所以成功很大程度上取决于传输协议HTTP协议的两大特性:
1、 简单。这个没什么可多说的。简单就是美,复杂的东西总是难以流行,想想古语所说阳春白雪和下里巴人就明白了。
2、 无状态。无状态意味着服务器可以满足更多次的请求,这在成天嚷着要带宽的互联网发展初期是确保www成功的重要特性。
但是到了互联网的深层次发展,带宽等限制不存在了,人们需要更复杂的互联网交互活动,就必须同服务器保持活动状态。于是,在浏览器大发展初期,适应人民群众的需求,技术上推出了各种保持web浏览状态的手段,其中就包括了Cookie技术。
总体上,按照状态保持存储信息的主要位置来讲,状态保持分为:
1、 服务器端存储状态数据。如通常意义上的session
2、 客户端保持存储,cookie技术和web页隐藏字段以及URL串存储。都是主要数据存储在客户端或者发往客户端的信息流中(HTML流),每一次客户汇报存储的数据提交给服务器,让服务器知道自己上一次访问服务器时候的状态。
3、 第三地存储。例如数据库存储会话数据,基本上是1的变种

cookie的存储和传输
关于cookie的规范可以查找:
http://www.w3.org/Protocols/rfc2109/rfc2109.txt
cookie实际上是一段经过了编码的字符串,具体来讲存储cookie的实际过程是以 key=value的形式存储的,而且一个cookie为一个连续的字符串,每一个key之间“;”分隔,cookie之间则以#分割。
在Http的header中,cookie被传输,按照如下的格式:
   set-cookie      =       "Set-Cookie:" cookies
   cookies         =       1#cookie
   cookie          =       NAME "=" VALUE *(";" cookie-av)
   NAME            =       attr
   VALUE           =       value
   cookie-av       =       "Comment" "=" value
                   |       "Domain" "=" value
                   |       "Max-Age" "=" value
                   |       "Path" "=" value
                   |       "Secure"
                   |       "Version" "=" 1*DIGIT
Cookie是同站点密切相关的,也就说,你的站点发放的Cookie是不会通过IE/NE等发送给其他的站点。这些浏览器是通过web站点来区分的。具体的是host来区分的。但是如果你指定了domain的属性,情况就不同了。那么只有指定domain的站点以及子站点(下级域名,注意还是域名)可以看到此cookie.
通常在IE中,Cookie是存放在IE进程的共享内存的。也就说,只要是统一进程的IE窗口,同一站点的cookie是共享的。

Cookie的消灭
直接给cookie赋””值是错误的,应当指示cookie的Max_age(ie是exprise)为一个过去值。譬如1980-1-1,这样,浏览器发现了这样的Cookie后就不会保存在内存中(更不会在辅存)这样才算清掉了cookie。我今天就遇到了这样的情形:
 我在服务器端设置失效期限为当前时间(那么理论上当语句执行完毕cookie就已经过期了),但是发送回客户端后,并没有(客户端时间之后服务期时间)。客户浏览器会认为这个cookie还有效而继续保持,在下一次提交时刻仍然提交到服务器,造成了逻辑处理上误解。

引用一篇关于asp.net的cookie的技术文章

 

(八)

Internet Explorer 事件

 

IWebBrowser2 的属性和方法给了你确切的控制导航和用户接口的途径,但是如果你不能够检测到浏览器正在处理什么以及何时处理什么,你还是没有全面的控制它.因此,WebBrowser控件和Internet Explorer暴露出事件,通过此你可以必要时监视活动以及处理某些活动. 举例来说,假设你建立一个intranet应用程序, 你想限制用户用户访问某些web页.利用Internet Explorer的时间处理句柄,你可以指令应用程序在用户试图访问受限的URL时候取消导航完成。

 

 

事件 和 引出的接口

无论何时一个COM 对象需要客户应用程序一个事件发生了, COM 对象发送一个叫做事件的消息. 发送消息的处理过程会激发一个事件. 但如果事件没有任何监听者会如何? 事件每次都发生吗? 显然, 客户应用程序监听这些事件并控制COM对象.如果一个客户应用想接收来自COM 对象的事件,它"advises" 实际的COM 对象的.

一个COM 对象为了通客户通信, 对象自身必须支持一个或者多个外引接口.一个 COM 对象支持的外引接口是作为可连接对象引用. 要成为一个可连接对象,COM对象必须实现IConnectionPointContainer接口。通过此接口,客户可认识到那些外引接口被服务器支持. 外引接口实际通过连接点由客户挂接入COM实现。实现外引接口的客户部分众所周知是通过事件接收槽(event sink)实现的.

单一的连接点由服务器支持每一个外引接口.每一个连接点能够操纵一种类型的外引接口且至少支持IConnectionPoint 接口. 图7-1 描述了可连接的对象和它的客户之间的关系.

Figure 7-1.Connectable object and its client.

每一个服务器必须实现2个接口以便实现客户可以接收事件—IConnectionPointContainerIConnectionPoint. 我们将先看看这些接口之后将检视实现事件的不同途径.

IConnectionPointContainer

每一个可连接对象实现了IConnectionPointContainer. 通过此接口, 试图接收事件的客户可找出关于可连接对象支持的不同的连接点. 通过调用QueryInterfaceusing 客户可以获得服务器支持的任何接口的指针. (你可以使用任何接口指针调用QueryInterface, 因为全部 COM 接口继承自IUnknown.) 之后客户可以使用IConnectionPointContainer 接口的2个方法中的之一获取可连接点。 如表7-1, 获得可连接点

Table 7-1 Methods of the IConnectionPointContainer Interface

方法

描述

EnumConnectionPoints

列举可连接对象支持的全部可连接对象

FindConnectionPoint

让客户查询可连接对象关于它是否支持一个特殊接口。客户指定特殊的接口(可连接点)的接口标示 (IID) 。如果可连接对象支持此接口,返回IConnectionPoint 接口的指针。

IConnectionPoint

一旦客户知道哪个连接点可被连接对象服务器支持, 客户就可建立同可连接对象的连接。客户通知可连接对象将要在全部事件中接收那些事件.当客户不再需要接收来自可连接对象的事件,客户解除对对象的通知. 表7-2 展示了IConnectionPoint 接口可被客户连接的. (大多数时候,你将连接到仅仅表中头两个)

Table 7-2 Methods of the IConnectionPoint Interface

方法

描述

Advise

在客户和可连接对象的某一个可连接点间建立连接。 客户必须传递它的事件接收槽的IUnknown 接口。 事件接收槽必须实现IDispatch 接口以接收事件。 典型地,党课连接对象激发事件,可连接对象将调用IDispatch 接口的Invoke 方法。 Advise 方法返回一个 cookie ,当客户中断连接时候,调用Unadvise方法时候需要携带此cookie

Unadvise

中断连接.

GetConnectionInterface

返回由连接点管理的外发接口的IID . GetConnectionInterface 方法让客户将IConnectionPoint翻译为一个IID.

GetConnectionPointContainer

得到刻连接对象的IConnectionPointContainer 接口

EnumConnections

枚举刻连接对象的当前可连接点.

 

   

接收事件的途径

依靠开发工具你创建客户应用程序,你可以接收事件通过不同的途径. 显然, 在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

 

(九)

接收事件的途径

依靠开发工具你创建客户应用程序,你可以接收事件通过不同的途径. 显然, 在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个参数, 但我们将仅仅讨论其中的两个:dispidMemberpDispParams. (其余的参见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 处理从VARIANTBSTR包装.

#include <strstrea.h>
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提供了两个函数—AtlAdviseAtlUnadvise—使得任何课连接对象的事件接收简单化.

AtlAdvise 函数告诉一个可连接对象客户想从此可连接对象接收事件.该函数封装实现接收事件的步骤1到3.AtlAdvise 理所当然省了大量的时间.就像IConnectionPoint::Advise 方法, AtlAdvise返回一个cookie供你稍后调用 AtlUnadvise. AtlUnadvise 告诉可连接对象客户不再接收事件.

让我们行说吧, 举个例子, ATL应用程序自动化Internet Explorer, 所以你想知道任何IE激发的事件. 为了告知Internet Explorer客户想接收事件,发出对AtlAdvise的以下调用:

HRESULT hr = AtlAdvise(m_spInetExplorer, GetUnknown(),
                       DIID_DWebBrowserEvents2, &m_dwCookie);

四个参数传递给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接收事件, 只需要调用 callAtlUnadvise, 如下:

HRESULT hr = AtlUnadvise(m_spInetExplorer,
                         DIID_DWebBrowserEvents2, 
                         m_dwCookie);

Figure 7-3.ATLIEEvtSpy.

以下展示如何自动化IE:

hr = CoCreateInstance(CLSID_InternetExplorer, NULL, CLSCTX_LOCAL_SERVER, 
                      IID_IWebBrowser2, (void**)&m_spInetExplorer);
if (SUCCEEDED(hr))
{
   m_spInetExplorer->put_Visible(TRUE);
   m_spInetExplorer->GoHome();        
 

接下来, AtlAdvise 调用以接收事件, 如下:

hr = AtlAdvise(m_spInetExplorer, GetUnknown(),
               DIID_DWebBrowserEvents2, &m_dwCookie);

CIEEvtObj 类继承自IDispatch,所以CIEEvtObj 类可以作为事件接收对象. Invoke 实现控制事件. 每当Internet Explorer 激发一个事件, 在listBox中显示一个消息.以西为invoke的代码:

STDMETHODIMP CIEEvtObj::Invoke(DISPID dispidMember, 
                               REFIID riid, 
                               LCID lcid, 
                               WORD wFlags, 
                               DISPPARAMS* pDispParams, 
                               VARIANT* pvarResult,
                               EXCEPINFO* pExcepInfo,  
                               UINT* puArgErr)
{
   _ASSERT(m_spInetExplorer);
 
   USES_CONVERSION;
   strstream strEventInfo;
 
   if (!pDispParams)
      return E_INVALIDARG;
 
   switch (dispidMember)
   {
      //
      // The parameters for this DISPID are as follows:
      // [0]: Cancel flag  - VT_BYREF|VT_BOOL
      // [1]: HTTP headers - VT_BYREF|VT_VARIANT
      // [2]: Address of HTTP POST data  - VT_BYREF|VT_VARIANT 
      // [3]: Target frame name - VT_BYREF|VT_VARIANT 
      // [4]: Option flags - VT_BYREF|VT_VARIANT
      // [5]: URL to navigate to - VT_BYREF|VT_VARIANT
      // [6]: An object that evaluates to the top-level or frame
      //      WebBrowser object corresponding to the event 
      //
      case DISPID_BEFORENAVIGATE2:
         strEventInfo << "BeforeNavigate2: ";
 
         if (pDispParams->cArgs >= 5
            && pDispParams->rgvarg[5].vt == (VT_BYREF|VT_VARIANT))
         {
            CComVariant vtURL(*pDispParams->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 << "CommandStateChange: ";
 
         if (pDispParams->cArgs == 0)
            strEventInfo << "NULL";
         else
         {
            if (pDispParams->cArgs > 1 
               && pDispParams->rgvarg[1].vt == VT_I4)
            {
               strEventInfo << "Command = " 
                            << pDispParams->rgvarg[1].lVal;
            }
 
            if (pDispParams->rgvarg[0].vt == VT_BOOL)
            {
               strEventInfo << ", Enabled = "
                      << ((pDispParams->rgvarg[0].boolVal == VARIANT_TRUE) 
                           ? "True" : "False");
            }
         }
 
         strEventInfo << ends;
         break;
 
      case DISPID_DOCUMENTCOMPLETE:
         strEventInfo << "DocumentComplete" << ends;
         break;
 
      case DISPID_DOWNLOADBEGIN:
         strEventInfo << "DownloadBegin" << ends;
         break;
 
      case DISPID_DOWNLOADCOMPLETE:
         strEventInfo << "DownloadComplete" << ends;
         break;
 
      //
      // 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:
         if (pDispParams->rgvarg[0].vt == (VT_BYREF|VT_VARIANT))
         {
            CComVariant vtURL(*pDispParams->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 << "ProgressChange: ";
 
         if (pDispParams->cArgs == 0)
            strEventInfo << "NULL";
         else
         {
            if (pDispParams->cArgs > 1 
               && pDispParams->rgvarg[1].vt == VT_I4)
            {
               strEventInfo << "Progress = " 
                            << pDispParams->rgvarg[1].lVal;
            }
 
            if (pDispParams->rgvarg[0].vt == VT_I4)
               strEventInfo << ", ProgressMax = " 
                            << pDispParams->rgvarg[0].lVal;
         }
 
         strEventInfo << ends;
         break;
 
      //
      // The parameter for this DISPID:
      // [0]: Name of property that changed - VT_BSTR
      //
      case DISPID_PROPERTYCHANGE:
         strEventInfo << "PropertyChange: ";
 
         if (pDispParams->cArgs > 0 
            && pDispParams->rgvarg[0].vt == VT_BSTR)
         {
            strEventInfo << OLE2T(pDispParams->rgvarg[0].bstrVal);
         }
         else
         {
            strEventInfo << "NULL";
         }
 
         strEventInfo << ends;
         break;
 
      //
      // The parameters for this DISPID:
      // [0]: New status bar text - VT_BSTR
      //
      case DISPID_STATUSTEXTCHANGE:
         LPOLESTR lpStatusText;
 
         m_spInetExplorer->get_StatusText(&lpStatusText);
         strEventInfo << "StatusTextChange: ";
 
         if (!strcmp(OLE2T(lpStatusText), ""))
            strEventInfo << "NULL";
         else
            strEventInfo << OLE2T(lpStatusText);
 
         strEventInfo << ends;
         break;
 
      case DISPID_NEWWINDOW2:
         strEventInfo << "NewWindow2" << ends;
         break;
 
      //
      // The parameter for this DISPID:
      // [0]: Document title - VT_BSTR
      //
      case DISPID_TITLECHANGE:
         strEventInfo << "TitleChange: ";
 
         if (pDispParams->cArgs > 0 
            && pDispParams->rgvarg[0].vt == VT_BSTR)
         {
            strEventInfo << OLE2T(pDispParams->rgvarg[0].bstrVal);
         }
         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 << "Unknown Event" << dispidMember << ends;
         break;
   }
 
   AddEventToList(strEventInfo.str());
 
   return S_OK;
}

请注意此使用了标准C++ 库的 strstream 类来建立字符串.这么做是因为ATL 不提供像Cstring的类. 每一次从IE接收到事件,建立一个包含事件的名称和参数的字符串. 然后显示在列表框中.

退出时候调用AtlUnadvise:

STDMETHODIMP CIEEvtObj::Stop()
{
   if (m_spInetExplorer)
   {
      HRESULT hr = AtlUnadvise(m_spInetExplorer, 
                               DIID_DWebBrowserEvents2, 
                               m_dwCookie);
 
      if (FAILED(hr))
         ATLTRACE("Failed to Unadvise\n");
   }
 
   PostQuitMessage(0);
   return S_OK;
}

在 MFC中接收事件

MFC提供了数个宏使得你可以接收从自动化的对象或者宿主的控件的事件。在两种情况中, 接收事件的类必须直接或者间接继承自CCmdTarget.CCmdTarget 实现接收事件的IDispatch 接口. 另外, 你必须在你的应用中调用EnableAutomation 初始化包含在CCmdTarget中的IDispatch.

在MFC中自动化一个COM 对象时接收事件

在mfc中接收事件很容易.全部要做的就是在代码中调用AfxConnectionAdvise函数以通告连接点客户需要接收事件.当客户不许要接收事件,调用AfxConnectionUnadviseAfxConnectionAdviseAfxConnectionUnadvise 函数定义于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 将看起来如下:

BEGIN_DISPATCH_MAP(CEventSink, CCmdTarget)

接下来用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, 如下使用:

DISP_FUNCTION_ID(CIE5Events, "DownloadComplete",
                 DISPID_DOWNLOADCOMPLETE, OnDownloadComplete,
                 VT_EMPTY, VTS_NONE)

最终采用 END_DISPATCH_MAP宏关闭.完整如下:

BEGIN_DISPATCH_MAP(CEventSink, CCmdTarget)
   DISP_FUNCTION_ID(CIE5Events, "DownloadComplete",
                    DISPID_DOWNLOADCOMPLETE, OnDownloadComplete,
                    VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()

在MFC中寄宿 ActiveX 控件时处理事件

这类似于处理COM对象的事件.主要区别在于你不需要通告或者解除通告连接点.CCmdTarget 未你控制了他.

在寄宿一个Activex控件情形中, CCmdTarget 使用事件接收宏代理派遣宏.就像你猜想的一样, MFC 提供初始化事件接收通告映射. 声明此宏类似声明派遣宏—派 DECLARE_EVENTSINK_MAP 宏存放在头文件中.另外的声明映射,  DECLARE_EVENTSINK_MAP 声明CCmdTarget 访问映射的类

接下来在类中实现事件接收.开始于EGIN_EVENTSINK_MAP 宏.指定事件接收类的 和它的基类。举例,此处为实例:

BEGIN_EVENTSINK_MAP(CMyDlg, CDialog)

现在实用ON_EVENT*宏来处理是按接收.。大多数情形,你将使用带有5个参数的ON_EVENT.携带的参数如下:

ON_EVENT(CMyDlg, IDC_WEBBROWSER, DISPID_DOWNLOADCOMPLETE, 
         OnDownloadComplete, VTS_NONE)

如果你象多个成员函数处理此事件, 使用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 宏.完整的定义如下:

BEGIN_EVENTSINK_MAP(CMyDlg, CDialog)
   ON_EVENT(CMyDlg, IDC_WEBBROWSER, DISPID_DOWNLOADCOMPLETE, 
            OnDownloadComplete, VTS_NONE)
END_EVENTSINK_MAP()

 

对于 DocumentComplete 事件,你应当如下声明:

// Declare the event sink map.  This declaration goes
// in the class declaration of CMFCIEEvtSpyDlg in the 
// MFCIEEvtSpyDlg.h header file.
//
DECLARE_EVENTSINK_MAP()
 
// Initialize the event sink map.  These macros
// go in the implementation file _ MFCIEEvtSpyDlg.cpp.
//
BEGIN_EVENTSINK_MAP(CMFCIEEvtSpyDlg, CDialog)
   ON_EVENT(CMFCIEEvtSpyDlg, IDC_WEBBROWSER, DISPID_DOCUMENTCOMPLETE,
            OnDocumentComplete, VTS_DISPATCH VTS_PVARIANT)
END_EVENTSINK_MAP()

Figure 7-4.MFCIEEvtSpy.

当WebBrowser 控件基于对话框应用,你通常不需要插入默认的宏, 因为 ClassWizard 可为你做这一切.而在SDI或者MDI工程中,需要加上此宏。

现在事件接收映射已经声明, 每当WebBrowser 控件激发了DocumentComplete事件, OnDocumentComplete 方法将被调用.在CMFCIEEvtSpyDlgOnDocumentComplete 方法中, 包含URL和事件名称的字符串被创建。之后字符串加入到列表框中展示WebBrowser 控件的事件.

以下代码解释如何接收处理DocumentComplete 事件.:

void CMFCIEEvtSpyDlg::OnDocumentComplete(LPDISPATCH pDisp, VARIANT* URL)
{
   USES_CONVERSION;
 
   CString strEvt("DocumentComplete: ");
   strEvt += OLE2T(URL->bstrVal);
 
   AddEventToList(WBListBox, strEvt);
}

当启动后,采用CoCreateInstance 创建的ie实例传递LSID_InternetExplorer接口..

以下为代码:

void CMFCIEEvtSpyDlg::OnStartIE() 
{
   if (m_pInetExplorer == NULL)  // Can start only one instance
   {
      // Create an instance of Internet Explorer. 
      //
      HRESULT hr = CoCreateInstance(CLSID_InternetExplorer,
                                    NULL,
                                    CLSCTX_LOCAL_SERVER, 
                                    IID_IWebBrowser2, 
                                    (void**)&m_pInetExplorer);
      if (SUCCEEDED(hr))
      {
         // Set up the event sink.
         //
         BOOL bAdvised = AfxConnectionAdvise(m_pInetExplorer, 
                                DIID_DWebBrowserEvents2,
                                m_pIE5Events->GetInterface(&IID_IUnknown),
                                TRUE, &m_dwCookie);
 
         // Disable the Start IE5 button so that the 
         // user knows that only one instance of 
         // Internet Explorer can be started at a time.
         //
         m_btnStartIE.EnableWindow(FALSE);
 
         // Make Internet Explorer visible and go home.
         //
         m_pInetExplorer->put_Visible(VARIANT_TRUE);
         m_pInetExplorer->GoHome();
      }
   }
}

为接收Internet Explorer 的事件,你应当声明派遣接口且在实现文中:

// Declare the dispatch map. This
// declaration is placed in the class declaration
// for the CIE5Events class, which is in the
// CIE5Events.h header file.
//
DECLARE_DISPATCH_MAP()
 
// Initialize the dispatch map in the
// implementation file for CIE5Events _ CIE5Events.cpp.
//
BEGIN_DISPATCH_MAP(CIE5Events, CCmdTarget)
   DISP_FUNCTION_ID(CIE5Events, "DocumentComplete", 
                    DISPID_DOCUMENTCOMPLETE, OnDocumentComplete, 
                    VT_EMPTY, VTS_DISPATCH VTS_PVARIANT)
END_DISPATCH_MAP()

现在无论如何接收到的自动化 Internet Explorer 的事件DocumentComplete, OnDocumentComplete方法将被调用. OnDocumentComplete 方法创建一个包含事件名称和URL的字符串,且加入到列表框通告Internet Explorer事件发生.同样期它事件发生也会如此处理.此处为CIE5Events类的OnDocumentComplete 方法代码:

void CIE5Events::OnDocumentComplete(LPDISPATCH pDisp, VARIANT* URL)
{
   USES_CONVERSION;
 
   CString strEvt("DocumentComplete: ");
   strEvt += OLE2T(URL->bstrVal);
 
   m_pParent->AddEventToList(CMFCIEEvtSpyDlg::IE5ListBox, strEvt);
}

 

 

(十)

由Internet Explorer 5激发事件

如你所知, Internet Explorer像其他COM对象一样激发事件—通过连接点.但实际上Internet Explorer如何激发事件呢?每次 Internet Explorer需要向客户提供关于当前活动状态的信息, Internet Explorer 激发通过DWebBrowserEvents2 连接点激发一个事件. (之前到版本 4, Internet Explorer 通过DWebBrowserEvents 接口激发事件.但到了版本4.x和5, Internet Explorer 通过 DWebBrowserEvents2 连接点.)

注意


如何领会到Internet Explorer加法那些事件?最佳途径是MSDN Online Web Workshop. 另外采用 OLE-COM Object Viewer

不像WebBrowser 控件 和 Internet Explorer的其他接口,是剑接口没有继承体系.DWebBrowserEvents 接口严格应用于Internet Explorer 3. 如果你正宿主WebBrowser 控件或者自动化Internet Explorer 5, 你可以通过此接口接收事件—但是不可挂接.DWebBrowserEvents2 接口包含的方法是为Internet Explorer 5定制的.用 DWebBrowserEvents2 替代DWebBrowserEvents ,你将有更多的控制能力. 所以不要忘记接口的最后面的2.

尽管DWebBrowserEvents2 是一个事件接口, 它其实就是像IWebBrowser2  一样的另外 COM 接口,所以它可以包含方法. (D 开头的命名是表示这是派遣接口.一个派遣接口是一个IDispatch 接口.但不同于普通接口, 派遣接口没有vtable.) 就像一个接口一样,派遣接口只不过提供一些函数的定义—他们并不真实实现.事件的实现由客户提供.举例来说,为了让WebBrowser 激发一个事件, 在DWebBrowserEvents2 接口中适当定义一些方法.这些方法由客户实现.但WebBrowser不直接调用这些方法.换句话讲, WebBrowser 并不调用DocumentComplete 方法.因为 DWebBrowserEvents2 是派遣接口, WebBrowser 通过IDispatch::Invoke调用客户的实现.早期, 当WebBrowser 调用客户的Invoke 实现, WebBrowser 传递事件被激发的DISPID.

注意


某些工具如Visual Basic, MFC, 和 ATL 提供Invoke实现

表7-6 列出 WebBrowser 事件. (这些是WebBrowser 控件和Internet Explorer供有的事件,尽管有些事件仅仅用于自动化Internet Explorer).

注意


尽管有些方法为不包含2.尽管DWebBrowserEvents2并非继承自WebBrowserEvents, DWebBrowserEvents2 还是匹配 DWebBrowserEvents 中被更改的,以免混淆。

注意表 7-6 重的参数有些值同样有 VARIANT_ TRUEVARIANT_FALSE. 如果你使用Visual C++,确信分派使用这些值不要使用 TRUE FALSE. 如果你使用Visual Basic, 它会自动帮助你转换,你可以比较True和False.

现在来仔细看看这些事件

Table 7-6 WebBrowser 事件

私有事件

描述

BeforeNavigate2

在导航之前发生. (该事件并不在不刷新页面的时候发生)

CommandStateChange

当命令状态改变时发生.该事件告诉你何时使能或者禁止 Back 以及Forward 菜单像或者按钮.

DocumentComplete

当整个文档完全完成装载时发生.如果你刷新页面, 此事件并不激发.

DownloadBegin

当一个下载项目开始时候发生 ,此事件也在你刷新(IWebBrowser2::Refresh.)时发生

DownloadComplete

党整个下载项目完成是发生该事件也发生在完成刷新页面.

NavigateComplete2

当整个导航完成. 该事件对应于 BeforeNavigate2.

NewWindow2

在一个新的窗口被创建以显示Web页或者其他资源的时候发生。譬如你在页面中以新建窗口的方式打开一个连接

OnFullScreen

FullScreen 属性被改变时候发生.此事件携带一个VARIANT_ BOOL类型的输入参数指示Internet Explorer 是否处于全屏(full-screen) 模式 (VARIANT_TRUE) 或者处于普通模式(VARIANT_FALSE).

OnMenuBar

菜单条MenuBar 属性被改变的时候发生. 一个VARIANT_ BOOL类型输入参数指  Internet Explorer的菜单条属性是可见(VARIANT_TRUE) 或者隐藏 (VARIANT_ FALSE).

OnQuit

当Internet Explorer正在退出时发生. 该事件当用户关闭浏览器或者调用 Quit 方法.

OnStatusBar

StatusBar 属性被改变的时候发生。事件携带VARIANT_ BOOL类型的输入参数 指示Internet Explorer的状态条是否可见 (VARIANT_TRUE) 或者隐藏 (VARIANT_FALSE).

OnTheaterMode

TheaterMode 属性被改变时发生. 事件携带VARIANT_ BOOL类型的输入参数 指示Internet Explorer的状态条是否可见 (VARIANT_TRUE) 或者隐藏 (VARIANT_FALSE).

OnToolBar

ToolBar属性被改变时发生. 事件携带VARIANT_ BOOL类型的输入参数 指示Internet Explorer的状态条是否可见 (VARIANT_TRUE) 或者隐藏 (VARIANT_FALSE).

OnVisible

当WebBrowser将被显示或者隐藏时发生。. 事件携带VARIANT_ BOOL类型的输入参数 指示Internet Explorer的状态条是否可见 (VARIANT_TRUE) 或者隐藏 (VARIANT_FALSE).

ProgressChange

当下载进度被更新时候发生

PropertyChange

当属性改变时候发生。典型的, 当PutProperty 方法被调用时

StatusTextChange

Internet Explorer 和 WebBrowser 控件改变状态条时候发生。即使webbrowser控件没有状态条。 StatusTextChange 给客户一个改变状态条的机会

TitleChange

.当文档对象的title可用或者改变的时候发生

BeforeNavigate2

BeforeNavigate2 就像字面上所说. 当Internet Explorer 导航到一个WEB页是激发; 因此, 当用户输入一个 URL, 点击 Back 或者 Forward 按钮, 或者处理一个导航时都会发生.BeforeNavigate2  也在WebBrowser 控件导航类方法调用时发生, 例如 Navigate, Navigate2,GoHome, 或者 GoSearch. 也许, 该事件不会在你刷新页面时发生. 如果页面上有帧, BeforeNavigate2 将像顶级窗口一样被激发.BeforeNavigate2 由7个输入参数, 见7-7.

Table 7-7 Input Parameters of the BeforeNavigate2 Event

Parameter

Description

pDisp

将发生导航的顶级窗口或者帧的Idispatch 接口的地址

URL

将导航至的URL

Flags

保留

TargetFrameName

显示资源的窗口或者帧的名字字符串,或者为NULL(如果没有命名)

PostData

HTTP POST 传输的数据地址

Headers

增加的将要发给服务器的 HTTP 头. 一般HTTP头指定其它的服务器要求。传送给服务器的数据类型、状态马等

Cancel

cancel 标志的地址. 设置为TRUE可取消导航

注意打所属参数匹配于Navigate 或者 Navigate2的调用参数.如果 BeforeNavigate2 由一个或者多个导航类调用激发,这些导航类的方法参数 传递到BeforeNavigate2方法.

在事件的句柄函数中, 你可以使用Cancel 参数取消导航, 或者你可以是用pDisp 参数修改导航目的.设置Cancel 参数为 VARIANT_TRUE 可以取消导航,如果你想,你可以通过pDisp修改参数导航信息且导航到另外的位置.举例来说,如果我们向停止当前导航, 增加一些头信息,且导航到原先的URL.在Visual Basic, 我们可以如下代码实现:

Private Sub WebBrowser1_BeforeNavigate2(ByVal pDisp As Object, _
                                        URL As Variant, _
                                        Flags As Variant, _
                                        TargetFrameName As Variant, _
                                        PostData As Variant, _
                                        Headers As Variant, _
                                        Cancel As Boolean)
 
   If TypeName(pDisp) = "WebBrowser" And Headers = "" Then
      pDisp.Stop
      pDisp.Navigate URL, Flags, TargetFrameName, PostData, _
                     Headers + "MyHeaders"
      Cancel = True
   End If
End Sub

我们需要注意以上代码的几个重点.

首先你必须检查pDisp 的类型使之确定为WebBrowser. 当页面包含帧,pDisp 可能不是 WebBrowser 对象由此一些导航会导致错误.

其次你必须检查确信Headers 参数是空字符串以避免无限循环.记住BeforeNavigate2 每一次导航都会发生.因此如果你在BeforeNavigate2事件处理中调用Navigate, 另外一次BeforeNavigate2 将被激活. 在前面的代码中, 仅仅在Headers 参数为空才调用Navigate 避免了无限循环. 如果Headers 参数是空,Navigate 将携带非空的 Headers 参数. 下一次BeforeNavigate2 北激活, Headers 将不为空; 此时,我们千万不可再次Navigate a,从而导致一个无限循环.

第三点你必须调用pDisp Stop 方法.如果你没做到这点,"about:NavigationCanceled" Web 页将会载你首次取消掉导航时被显示.

CommandStateChange

CommandStateChange 是当Internet Explorer 想通知一个应用程序WebBrowser 命令状态已经改变时激发.当检测到Forward和Back 菜单项和按钮禁止或者使能时激发此事件.

CommandStateChange 事件有2个参数,CommandEnable。 Command 输入参数将要改变状态的按钮的表示符,可以取值—CSC_NAVIGATEFORWARDCSC_NAVIGATEBACK分别表示是Forward按钮项和Back项,每次导航发生, CommandStateChange 事件发生并告诉你Forward 或者 Back 菜单项以及按钮将使能或禁止. 举例来说, 如果没有Web页在当前导航后发生,Command 的值应该为 CSC_NAVIGATEFORWARD, 并且Enable 参数将等于VARIANT_FALSE.

第二个参数, Enable,如果命令可用(使能) 则为VARIANT_TRUE,如果禁止则值为VARIANT_FALSE.

 

为说明问题,我们看看代码。一下代码为定义事件接收的宏声明:

 
// Event sink map declaration for WebBrowser 
// control events. This declaration goes in the
// header file for CMfcWebHostView _ MfcWebHostView.h.
//
DECLARE_EVENTSINK_MAP()
 
// Initialize the event sink map and handle the 
// CommandStateChange event.
BEGIN_EVENTSINK_MAP(CMfcWebHostView, CView)
   ON_EVENT(CMFCIEEvtSpyDlg, IDC_WEBBROWSER, DISPID_COMMANDSTATECHANGE,
            OnCommandStateChange, VTS_I4 VTS_BOOL)
END_EVENTSINK_MAP()

重要的一点是要通过WebBrowser控件的Create 方法来创建. ON_EVENT的第二个参数为你宿主的WebBrowser控件的IID.之前的例子中是NULL. 你必须为webbrowser控件声明一个ID 且用此 ID 创建实例.如果不这么做,事件将不会正确工作.

你可以在工程的任何文件定义此ID. (推荐在资源头文件resource.h.) 因为 最大的资源Id是32,779, 所以你可以如下定义WebBrowser 控件的ID:

#define IDC_WEBBROWSER  35000

此数高于resource.h中的任何资源ID, 因此可以确信ID 数字不会同采用ClassWizard添加的ID冲突.现在可以采用使用ID的Create 方法来创建WebBrowser控件:

if (!m_webBrowser.Create(NULL, WS_CHILD|WS_VISIBLE,
                         CRect(), this, IDC_WEBBROWSER))
{
   return -1;
}

下一步声明OnCommandStateChange 方法,该方法将在WebBrowser控件激发CommandStateChange事件时被调用.可声明 如下:

void OnCommandStateChange(long lCommand, BOOL bEnable);

OnCommandStateChange 方法的是现代码中, 设定表示Go Forward 或者 Go Back 想得导航菜单项的数据成员为使能或者禁止. 该书据成员将被UPDATE_COMMAND_UI 句柄使用。以下为OnCommandStateChange 方法实现:

void CMfcWebHostView::OnCommandStateChange(long lCommand, BOOL bEnable)
{
   switch(lCommand)
   {
      // Forward command
      //
 
      case CSC_NAVIGATEFORWARD:
         m_fForwardEnabled = bEnable;
         break;
 
      // Back command
      //
      case CSC_NAVIGATEBACK:
         m_fBackEnabled = bEnable;
         break;
 
      default:
         break;
   }
}

声明m_fForwardEnabledm_fBackEnabled 数据变量为保护成员,类型为BOOL.同样在构造函数中谁的些数据成员为TRUE。.

现在当Go Forward和Go Back的菜单 UPDATE_COMMAND_UI被处理, 你可以直接进行设置. 下为示例代码:

void CMfcWebHostView::OnUpdateNavigateGoForward(CCmdUI* pCmdUI) 
{
   pCmdUI->Enable(m_fForwardEnabled);
}
 
void CMfcWebHostView::OnUpdateNavigateGoBack(CCmdUI* pCmdUI) 
{
   pCmdUI->Enable(m_fBackEnabled);
}

DocumentComplete

当一个文档完整的完成下载Internet Explorer 激发DocumentComplete 事件. 仅仅当此事件激发后 文档对象才可安全使用.在一个无帧的Web页情形中文档对象是IHTMLDocument2对象, 我们以后会讨论. 当文档对象准备好可用,他的状态为READYSTATE_COMPLETE.

关于 DocumentComplete 事件以西击点需要注意:

·         在没有帧的web页, DocumentComplete 事件在下载完成后激发一次.

  • 在多帧的web页,此事件激发多次.并非每一个帧激发一个事件, 但每一个帧激发DownloadBegin事件将会相应激发DocumentComplete 事件.
  • DocumentComplete又一个指向IDispatch 的指针参数, 该参数指向激发此事件的窗口. 此窗口可以是帧中的窗口
  • 顶级帧在所有子帧激发了各自的DocumentComplete事件后激发自己的DocumentComplete事件。 因此,,要看一个web页是否完整下载完成, 你需要从该事件的处理句柄中获取由事件产地过来的IDispatch 参数的IUnknown 接口。下一步,比较IUnknown接口是否指向你正宿主的WebBrowser控件或者自动化的IE的实例的IUnknown 接口.如果这两个指针相同,这意味着全部HTML, 图片images, 控件,以及诸如此类在顶级帧或者子帧的全部对象元素都被下载了.

 

VB中实现以上四点及其容易.仅需要检查发送给事件的pDisp 参数事一个WebBrowser 对象. Visual Basic小心检查这些对象的Iunknown否为同一个对象.此处为VB代码::

Private Sub WebBrowser1_DocumentComplete(ByVal pDisp As Object,
                                         URL As Variant)
   If (pDisp Is WebBrowser1.Object) Then
      MsgBox "The document is finished loading."
   End If
End Sub

实现以上四点在Visual C++ 应用程序里较困难一点,但你可以做到! 首先在DocumentComplete事件的宏中如下声明:

ON_EVENT(CMfcWebHostView, IDC_WEBBROWSER, DISPID_DOCUMENTCOMPLETE,
         OnDocumentComplete, VTS_DISPATCH VTS_PVARIANT)

接下来声明OnDocumentComplete 方法作为事件处理句柄

void OnDocumentComplete(LPDISPATCH lpDispatch, VARIANT FAR* URL);

最后,实现该方法以检测 是否页面已经下载,我们得到我们宿主控制bBrowser 控件的IUnknown. (注意我们不是简单获取指向IUnknown ,而是要调用GetControlUnknown 方法. GetControlUnknown方法返回的IUnknown指针 实际上并不等于被宿主话的 WebBrowser控件的IUnknown. 那将返回IOleObject 接口指针.) 下一步, 获取IUnknown指针,如果QueryInterface 查询得到的Dispatch 参数同Iunknown接口是同一对象,则页面完成整个下载。.

void CMfcWebHostView::OnDocumentComplete(LPDISPATCH lpDispatch,
                                         VARIANT FAR* URL)
{
   HRESULT   hr;
   LPUNKNOWN lpUnknown;
   LPUNKNOWN lpUnknownWB = NULL;
   LPUNKNOWN lpUnknownDC = NULL;
 
   lpUnknown = m_webBrowser.GetControlUnknown();
   ASSERT(lpUnknown);
 
   if (lpUnknown)
   {
      // Get the pointer to the IUnknown interface of the WebBrowser 
      // control being hosted. The pointer to the IUnknown returned from 
      // GetControlUnknown is not the pointer to the IUnknown of the 
      // WebBrowser control. It's actually a pointer to the IOleObject.
      // 
      hr = lpUnknown->QueryInterface(IID_IUnknown,
                                     (LPVOID*)&lpUnknownWB);
 
      ASSERT(SUCCEEDED(hr));
 
      if (FAILED(hr))
         return;
 
      // Get the pointer to the IUnknown of the object that fired this 
      // event.
      //
      hr = lpDispatch->QueryInterface(IID_IUnknown, 
                                      (LPVOID*)&lpUnknownDC);
 
      ASSERT(SUCCEEDED(hr));
 
      if (SUCCEEDED(hr) && lpUnknownWB == lpUnknownDC)
      {
         // The document has finished loading.
         //
         MessageBox("The document has finished loading.");
      }
 
      if (lpUnknownWB)
         lpUnknownWB->Release();
 
      if (lpUnknownDC)
         lpUnknownDC->Release();
   }
}

有一点需要注意上面的代码我们在GetControlUnknown 返回的IUnknown 接口指针使用时并没有进行Release ,因为bIUnknown 指针并没有在GetControlUnknown方法中 AddRef'. GetControlUnknown 方法仅仅返回一个IOleObject 数据成员的指针,该指针由控件站点类—CcontrolSite操纵处理. 如果你释放了IUnknown 接口指针, 载你关闭应用程序时,一个访问违例将会发生,因为MFC 将试图在对象被删除时候多释放一次.

DownloadBegin

DownloadBegin 事件通知应用程序一个导航操作开始. 一般情况下该事件在BeforeNavigate2事件之后激发, 除非导航操作在BeforeNavigate2 事件处理过程中被取消.容器应当显示动画或者忙指示当前正处于连接的DownloadBegin事件. 每一个DownloadBegin 事件有一个相应的DownloadComplete 事件. 在刷新页面的情形中, DownloadBeginDownloadComplete 使唯一的被激发的导航事件.

DownloadComplete

DownloadComplete在一个导航操作完成时候发生, 停止, 或者失败. 不像NavigateComplete2仅仅当成功导航才发生, DownloadComplete总是在道涵开始后激发.任何在DownloadBegin中显示的动画或者忙指示将会在DownloadComplete 中停止.

NavigateComplete2

NavigateComplete2 事件在导航到一个超连接整个窗口或者帧集合的元素全部完成时候发生. 第一此事件发生表示文档document已经准备好.在此事件发生后, 你可以通过Document属性存取文档(document)而不接收到错误.但是能够访问一个文档不意味着你访问文档使安全的.你可以在DocumentComplete 事件激发后安全访问文档.

档你需要访问document对象但是不需要访问文档内的元素,你可以在NavigateComplete2 事件中尽可能快的处理,例如当你在文打工通过高级宿主接口.NavigateComplete2 事件有2个参数—IDispatch of 代表激发事件的对象URL 为你需要导航到的URL.

NewWindow2

NewWindow2 档用户显示一个新窗口以进行新导航显示web页或者其他资源时发生.在WebBrowser控件响应柄进行预处理 (举例来说, 在响应window.open方法).

NewWindow2 也在Navigate或者 Navigate2 方法被调用且navOpenInNewWindow 标志被设定时发生. 档采用文件菜单中的New Window按钮时并不发生(Internet Explorer帧不是一个 HTML 帧; 它是帧窗口.) 因此,WebBrowser 对象不知道什么时候新窗口将被打开. 因为 NewWindow2 有时候很难使用, 所以我们来检查它的两个参数:ppDisp and Cancel.

ppDisp 参数是接口指针, 一般是接收新WebBrowser 或者InternetExplorer 对象的IDispatch 接口指针, 是你能够创建一个Internet Explorer新实例以便能够控制来自你的应用程序导航产生的新窗口. 该实例开始为新建的, 隐藏的, (暂时)不可导航WebBrowser 或者InternetExplorer 对象. 在NewWindow2事件句柄函数返回之前, InternetExplorer 对象激发NewWindow2 事件将配置新WebBrowser对象的导航目标位置.

另外参数, Cancel, 时取消(Cancel)标志的地址. 应用程序能够设定此参数为TRUE 以取消导航操作或者设定为FALSE 以允许新建窗口操作. 设定CancelTRUE 完全取消新建窗口操作和导航.

如果你不在NewWindow2 事件处理过程中作任何事, 新的InternetExplorer 对象将自动建立. 一些原因你想控制NewWindow2 事件以便控制新建InternetExplorer 对象. 为什么? 因为你想限制Internet Explorer的实例数量,或者你想控制创建的实例的事件.

以下 NewWindow2 事件控制函数中; 建立了一个新的, 隐藏的, 不可导航的Internet Explorer实例; 并且设定ppDisp 参数指向新实例.如果你想,你可以加入任何接收新实例事件的代码.

void CMyEvtSink::NewWindow2(LPDISPATCH* ppDisp, BOOL* Cancel)
{
   // Note that m_pIE is a class member of type IWebBrowser2*.
   HRESULT hr = CoCreateInstance(CLSID_InternetExplorer, NULL, 
                                 CLSCTX_LOCAL_SERVER, IID_IWebBrowser2, 
                                 (void**)&m_pIE);
   if (hr == S_OK)
      *ppDisp = (IDispatch*)pIE;
 
   // Do not set Cancel to TRUE. If you do,
   // the navigation will be completely canceled.
}

另外的原因控制NewWindow2 事件是由于你想你应用程序在用户选择在新窗口打开一个url时进行控制. 如果你不进行控制NewWindow2事件, Internet Explorer 新实例将被创建.

以下为控制新建窗口的vb代码:

Private Sub WebBrowser1_NewWindow2(ppDisp As Object, Cancel As Boolean)
   Dim frmWB As Form1
   Set frmWB = New Form1
 
   Set ppDisp = frmWB.WebBrowser1.Object
   frmWB.Visible = True
 
   Set frmWB = Nothing
End Sub

在此NewWindow2事件代码中,档一个新常口需要被创建, 我们建立一个新的当前窗体Form1的拷贝. 在此表单窗体, 相当于Internet Explorer的新实例,将处理导航.

在mfc中我们需要首先加入NewWindow2 事件的映射条目到视图类的事件映射宏. (不要忘记包含 ExDispID.h in, 那里有DISPID_NEWWINDOW2定义.)

ON_EVENT(CMfcWebHostView, IDC_WEBBROWSER, DISPID_NEWWINDOW2,
         OnNewWindow2, VTS_PDISPATCH VTS_PBOOL)

下一步声明OnNewWindow2 方法:

void OnNewWindow2(LPDISPATCH* ppDisp, BOOL* Cancel);

最后实现OnNewWindow2 方法以创建一个新的MfcWebHost窗口实例:

void CMfcWebHostView::OnNewWindow2(LPDISPATCH FAR* ppDisp,
                                   BOOL FAR* Cancel)
{
   // Ensure that ppDisp is not NULL.
   // If it is NULL, you probably specified
   // VT_DISPATCH for the first parameter in 
   // the ON_EVENT macro for NewWindow2 in 
   // the event sink map. The correct parameter
   // type is VT_PDISPATCH.
   //
   ASSERT(ppDisp);
   if (!ppDisp)
      return;
 
   // Get a pointer to the application object
   // for this application.
   //
   CWinApp* pApp = AfxGetApp();
 
   // Get the correct document template.
   //
   CDocTemplate* pDocTemplate;
   POSITION pos = pApp->GetFirstDocTemplatePosition();
   pDocTemplate = pApp->GetNextDocTemplate(pos);
 
   ASSERT(pDocTemplate);
 
   // Create the new frame.
   CFrameWnd* pNewFrame = pDocTemplate->CreateNewFrame(GetDocument(),
                                            (CFrameWnd*)AfxGetMainWnd());
   ASSERT(pNewFrame);
 
   // Activate the frame, and set its active view.
   //
   pDocTemplate->InitialUpdateFrame(pNewFrame, NULL);
 
   CMfcWebHostView* pWbView = 
                    (CMfcWebHostView*)pNewFrame->GetActiveView();
   
   ASSERT(pWbView);
 
   *ppDisp = pWbView->m_webBrowser.GetApplication();
}

如果你在sid或者mdi应用程序中控制一个WebBrowser控件,实现OnNewWindow2 方法是复杂的且需要知道如何解决同文档模版如何工作. 或许, 如果你在一个给予对话框的应用程序控制一个WebBrowser控件是较为容易的.此处为示例:

void CMyDlg::OnNewWindow2(LPDISPATCH FAR* ppDisp, BOOL FAR* Cancel) 
{
   m_dlgNewWB = new CMyDlg;
   m_dlgNewWB->Create(IDD_MYDLG_DIALOG);
 
   *ppDisp = m_dlgNewWB->m_webBrowser.GetApplication();
}

记住当你完成打开的新对话框后删除(delete) m_dlgNewWB. 且不要在CMyDlg::OnInitDialog方法中导航, 因为这样代码将不会工作.

                           

 

ProgressChange

ProgressChange 事件通告你的应用程序下在操作状态已经更新.ProgressChange 有两个参数:

·         Progress. 总计有多少进度将被展示, 如果为-1 表示整个进度已经完成

  • ProgressMax.最大进度值

容器可通过此事件显示下载进度。

 

事件发生序列

下图展示了IE的事件发生序列.但这仅仅为不包含帧的普通网页浏览. (没有包含诸如ProgressChange, CommandStateChange, OnToolBar,等等事件.)不是所有事件都会被激发. 但是BeforeNavigate2DocumentComplete 每次浏览都会被激发.

 

   

Figure 7-5.The sequence of events fired by the WebBrowser control during a typical navigation.

 

-- 不使用 Cookie 的 ASP.NET 会话管理:风险与利益

发布日期: 7/18/2005 |更新日期: 7/18/2005

Dino Esposito
Wintellect

摘要:Dino 探究无 Cookie 会话的优缺点,并且讨论为什么应该避免在会话状态中存储有价值的信息。

本页内容

Cookies 是不是一个问题?

进入无 Cookie 会话

实现

优点

缺点

小结

我们承认这一点 — 我们对会话状态这一概念是如此习以为常,以至于我们忘记了会话状态是在 1997 年用 Active Server Pages (ASP) 引入的一个手段。会话状态使开发人员能够在用户与应用程序交互这段时间内持久保存有关该用户的一块信息。特定于用户的信息通常会保留 20 分钟长的时段,而每当用户返回该站点时,该时段都将重新开始计时。

当用户首次连接到站点时,将以内存块的形式创建一个全新的会话状态以存放数据,同时,还会创建一个 ID 以便将其与当前用户唯一地联系起来。当下一次发出请求时,该用户将被要求提交该会话 ID,以便检索并正确地还原会话状态。会话 ID 是 ASP 和 ASP.NET 完全自主生成的字母数字字符串。用户如何管理它并确保用每个后续请求来包装它呢?

HTTP 协议的性质是无状态的,并且没有任何人试图更改这一事实。差不多二十年以前,当 Netscape Corporation 开发它的第一个浏览器时,它“发明”了一种通过 HTTP 工作的持久性机制。它将其称为 HTTP Cookie。有趣的是,计算机科学行话中的术语“Cookie”仅仅表示一块由应用程序持有的不透明数据,它会影响用户但永远不会由用户直接管理。

因此,Cookie 存储会话的 ID,而浏览器则在 Web 服务器和本地用户的计算机之间来回移动它们的内容。当启用了 Cookie 的浏览器收到响应数据包时,它将寻找附加的 Cookie,并将它们的内容存储到本地 Windows 目录中特定文件夹的某个文本文件中。Cookie 还包含有关该源站点的信息。接下来,当浏览器向该站点发送请求时,它会在 Cookie 文件夹中查找源自该域的 Cookie。如果找到,则该 Cookie 自动附加到传出的数据包中。该 Cookie 将命中服务器应用程序,并在此被检测、提取和处理。

最终,Cookie 使 Web 站点更加易于导航,因为它们在用户体验之上提供了必然跨越多个请求的连续性错觉。

Cookies 是不是一个问题?

多年以来,Cookie 只被视为一种技术功能,并且在很大程度上被忽略了。几年以前,针对 Web 安全的世界范围的浪潮将人们的注意力集中于 Cookie 身上。Cookie 被断定包含危险的程序,它们甚至能够超出计算机的物理边界来窃取有价值的信息。

不言而喻,Cookie 不是程序,因而无法自行收集任何信息 — 更不用说有关用户的任何个人信息。更加清楚的是,Cookie 是 Web 站点可以放置在用户的计算机中以便以后检索和重用的一段文本。所存储的信息是由无害的名称-值对组成的。

要点在于,Cookie 不是标准 HTTP 规范的一部分,因此它们意味着浏览器和 Web 站点之间的一种协作。并非所有浏览器都支持 Cookie,而且更为重要的是,并非所有用户都在他们自己的浏览器副本中启用 Cookie 支持。

在历史上,有一些 Web 站点功能是如此紧密地与 Cookie 相联系,以至于很难区分究竟是哪个功能最先出现。一方面,用 Cookie 对会话状态管理和用户身份验证进行编码要容易得多。另一方面,如果您观察一下站点与用于访问页的浏览器有关的统计信息,那么您可能会惊讶地发现,相当一部分用户在连接时禁用了 Cookie。这一点会对开发人员有所启示。

总而言之,Cookie 本身并不是问题,但它们的使用无疑给予一些服务器代码在客户端计算机中存储一段数据的能力。这预示着一些潜在的安全风险和一种不够理想的总体状况。(在某些情况以及某些国家/地区中,应用程序要求 Cookie 工作甚至是非法的。)

返回页首

进入无 Cookie 会话

在 ASP.NET 中,无需使用 Cookie,就可以有选择地建立必要的会话-用户联系。非常有趣的是,除了以下配置设置以外,您无需在 ASP.NET 应用程序中更改任何内容即可启用无 Cookie 会话。

<sessionState cookieless="true" /> 

ASP.NET 会话状态的默认设置是在 machine.config 文件中定义的,并且可以在应用程序根文件夹中的 web.config 文件中重写。通过确保上述行出现在根 web.config 文件中,您可以启用无 Cookie 会话。就是这样 — 简单而有效!

<sessionState>节点还可以用于配置会话状态管理的其他方面,包括存储介质和连接字符串。但是,就 Cookie 而言,只需您将cookieless 属性设置为 true(默认设置为 false)。

请注意,会话设置是应用程序范围的设置。换句话说,您站点中的页要么都将使用要么都将不使用 Cookie 来存储会话 ID。

当不使用 Cookie 时,ASP.NET 在哪里存储会话 ID 呢?在这种情况下,会话 ID 插入到 URL 内的特定位置中。下图显示一个使用无 Cookie 会话的真实站点的快照。

图 1. 使用无 Cookie 会话的 MapPoint

假设您请求了一个类似于 http://yourserver/folder/default.aspx 的页。正如您可以从 MapPoint 快照中看到的那样,资源名称前面的相邻斜杠进行了扩展,以便包含在内部填充了会话 ID 的括号,如下所示。

http://yourserver/folder/(session ID here)/default.aspx

会话 ID 嵌入到 URL 中,并且无需在其他任何地方持久保存它。唔,并不完全是这样。请考虑以下方案。

您访问了一个页,并且被分配了一个会话 ID。接下来,您清除了同一浏览器示例的地址栏,转到另一个应用程序并且开始工作。然后,您重新键入了上一个应用程序的 URL,并且(猜猜看)在您进入的过程中检索会话值。

如果您使用无 Cookie 会话,那么当您第二次访问该应用程序时,您将被分配一个不同的会话 ID,并且丢失以前的所有状态。这是无 Cookie 会话的一个典型的副作用。为了了解其原因,让我们进一步探讨无 Cookie 会话的实现。

返回页首

实现

无 Cookie 会话的实现得益于下列两个运行时模块的努力:一个名为 SessionStateModule 的标准会话 HTTP 模块,以及一个名为 aspnet_filter.dll 的可执行文件。后者是一小段 Win32 代码,它充当 ISAPI 筛选器。HTTP 模块和 ISAPI 筛选器实现了相同的思想,不同之处在于 HTTP 模块由托管代码组成,并且需要 ASP.NET 和 CLR 触发才能工作。像 aspnet_filter.dll 这样的传统 ISAPI 筛选器是由 Internet 信息服务 (IIS) 调用的。二者都截获在请求处理过程中激发的 IIS 事件。

当新浏览器会话的第一个请求进入时,会话状态模块读取 web.config 文件中有关 Cookie 支持的设置。如果 节的 cookieless 属性设置为 true,则该模块生成一个新的会话 ID,通过将该会话 ID 填充到资源名称前面的相邻位置来分割 URL,并且使用 HTTP 302 命令将浏览器重定向到新的 URL。

当每个请求到达 IIS 入口时(远远早于它被移交给 ASP.NET),aspnet_filter.dll 获得了一个查看它的机会。如果该 URL 将会话 ID 嵌入到括号中,则会提取该会话 ID 并将其复制到一个名为AspFilterSessionId 的请求标头中。然后,重写该 URL 以使其看起来像原来请求的资源,并且将其释放。这一次,ASP.NET 会话状态模块从请求标头中检索会话 ID,并且通过会话-状态绑定继续工作。

只要该 URL 包含可用来获取会话 ID 的信息,无 Cookie 机制就可以很好地工作。正如您稍后将看到的那样,这会造成一些使用限制。

让我们研究一下无 Cookie 会话的优缺点。

返回页首

优点

在 ASP.NET 中,会话管理和表单身份验证是唯一的两个在后台使用 Cookie 的系统功能。通过无 Cookie 会话,您现在可以部署无论用户的有关 Cookie 的首选项如何都能正常工作的有状态应用程序。然而,就 ASP.NET 1.x 而言,仍然需要使用 Cookie 来实现表单身份验证。好消息是,在 ASP.NET 2.0 中,表单身份验证可以选择以无 Cookie 方式工作。

另一个经常提出的反对 Cookie 的理由是安全性。这是一个值得予以更多关注的要点。

Cookies 是无活动能力的文本文件,因此,这些文件可能被攻击者替换或损坏 — 只要他们获得了对计算机的访问。真正的威胁并不在于 Cookie 可以在客户端计算机上安装什么,而是在于它们可以向目标站点上载什么。Cookie 不是程序,并且永远不会像程序那样运行;然而,您计算机上安装的其他软件可以使用对 Cookie 的浏览器内置支持来远程从事破坏活动。

此外,Cookie 要受到被盗窃的风险。一旦失窃,包含有价值的和私人的信息的 Cookie 就可能将其内容泄露给恶意攻击者,并且为其他类型的 Web 攻击提供便利。总之,通过使用 Cookie,您将自己暴露在本可以消除的风险之中。这是真的吗?

返回页首

缺点

让我们从另一个角度来考察安全性。您是否曾经听说过会话劫持?如果没有,则请阅读一下 TechNet Magazine 文章 Theft On The Web: Prevent Session Hijacking。简单说来,当攻击者获得对特定用户的会话状态的访问时,将发生会话劫持。其实质是,攻击者窃取有效的会话 ID,并且使用它侵入系统和窥探数据。获取有效会话 ID 的一种常见方式是窃取有效的会话 Cookie。鉴于此,如果您认为无 Cookie 会话保护了您应用程序的安全,那您就完全错了。实际上,对于无 Cookie 会话,会话 ID 直接显示在地址栏中!请尝试下列操作:

1.

连接到使用无 Cookie 会话的 Web 站点(例如,MapPoint)并获得一个映射。此时,该地址存储在会话状态中。

2.

抓取 URL(直至页名称)。不要包括查询字符串,但请确保该 URL 包括会话 ID。

3.

将该 URL 保存到文件中,并将该文件复制/发送到另一台计算机。

4.

在第二台计算机上打开该文件,并将该 URL 粘贴到新浏览器实例中。

5.

只要会话超时仍然有效,就会显示同一个映射。

通过无 Cookie 会话,可以比以往任何时候都更加容易地窃取会话 ID。

从道德的观点来看,窃取会话是应该受到谴责的操作,我相信大家会一致认同这一点。但它是否也是有害的?这取决于会话状态中实际存储的内容。窃取会话 ID 本身并不会执行超出代码控制范围的操作。但是,它可能向未经授权的用户泄露私有数据,并且使一些坏家伙能够执行未经授权的操作。有关如何在 ASP.NET 应用程序中阻止会话劫持的提示,请阅读Wicked Code: Foiling Session Hijacking Attempts。(而且,它并不依赖于无 Cookie 会话!)

使用无 Cookie 会话还会引起与链接有关的问题。例如,您不能在 ASP.NET 页中具有绝对的、完全限定的链接。如果您这样做,那么源自该超链接的每个请求都将被视为新会话的一部分。无 Cookie 会话要求您总是使用相对 URL,就像在 ASP.NET 回发中一样。仅当您可以将会话 ID 嵌入到 URL 中时,您才可以使用完全限定的 URL。但是,既然会话 ID 是在运行时生成的,那么您如何才能做到这一点呢?

下面的代码中断了该会话:

<a runat="server" href="/test/page.aspx">Click</a>

要使用绝对 URL,可以借助于一个小技巧,即,使用 HttpResponse 类上的 ApplyAppPathModifier 方法:

<a runat="server" href=<% =Response.ApplyAppPathModifier("/test/page.aspx")%> >Click</a> 

ApplyAppPathModifier 方法采用一个表示 URL 的字符串作为参数,并且返回一个嵌入了会话信息的绝对 URL。例如,当您需要从 HTTP 页重定向到 HTTPS 页时,该技巧尤其有用。最后,请特别注意,每当您在同一个浏览器内部键入指向某个站点的路径时,您都将丢失无 Cookie 会话的状态。还要请您注意的是,对于移动应用程序,如果设备无法处理专门格式化的 URL,则无 Cookie 会话可能会出现问题。

返回页首

小结

ASP.NET 中存在无 Cookie 会话的主要原因是用户(无论出于什么原因)可能在他们的浏览器中禁用了 Cookie。如果您的应用程序需要会话状态,那么无论您是否喜欢,您都必须面对这种情况。无 Cookie 会话将会话 ID 嵌入到 URL 中,并且得到了双重结果。一方面,它们为 Web 站点提供了一种正确标识发出请求的用户的方式。然而,另一方面,它们使会话 ID 清楚地显现在潜在的攻击者面前,从而使攻击者可以轻松地窃取它并以您的身份进行操作。

要实现无 Cookie 会话,您无需修改自己的编程模型 — 只需在 web.config 文件中进行简单更改,就可以完成相关工作 — 但是,还要强烈建议您重构您的应用程序,以免在会话状态中存储有价值的信息。同时,将会话的生存期缩短至默认的 20 分钟以内有助于保护您的用户和站点的安全。

(十一)

仅仅用于Internet Explorer的事件

有些是仅仅可用于自动化 Internet Explorer,:

·         OnQuit

  • OnVisible
  • OnToolBar
  • OnMenuBar
  • OnStatusBar
  • OnFullScreen
  • OnTheaterMode

大多数这些事件属于浏览器用户接口. 另外一些必须要先是或者关闭Internet Explorer才发生. 一些情形中,这些事件将在你宿主webbrowser空间的时候发生. 举例来讲,当你在你的应用程序设置MenuBar 属性,尽管你的WebBrowser control 并没有菜单条,OnMenuBar 事件将被激发, 但是如果你显示或者隐藏你的应用程序菜单条,OnMenuBar 事件不会激发.为什么?因为你的菜单条由你控制,webbrowser对这些用户接口项一无所知. 很长时间以来,这些相互矛盾的功能是一些混乱的根源。

其中一个事件—OnQuit—将永远不会在你的应用程序中激发.举个例子, 察看表 Table 7-6. 注意到OnQuit事件当用户关闭 Internet Explorer 或者当Quit 方法被调用时激发.如果你宿主改控件且用户关闭你的应用程序,  OnQuit 事件不会激发.它仅仅在你自动化Internet Explorer 且用户手动关闭浏览器时候发生.另外,如果你在宿主一个webbrowser控件时试图调用Quit方法,一个自动化错误将会发生.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

自ActiveX控件中控制Internet Explorer 事件

通过 IWebBrowser2 接口你可以在利用vc++在ActiveX 控件中接受事件.

你可能疑惑为什么要在ActiveX控件中接受 Internet Explorer事件.之前介绍"DocumentComplete," 事件时候,我提到过你不可以在DocumentComplete event 事件被触发前安全存取文档.在Activex控件中获知DocumentComplete 事件被触发的途径是ActiveX 控件接收 Internet Explorer并处理 DocumentComplete 事件.

除了你必须接收Internet Explorer 事件外, 你可以开发一个可导航的类浏览器的应用于公司intranet或者学校网络.你可以在ActiveX control中自动化Internet Explorer并接受其事件。.

当你刷新一个页面, 也许DocumentComplete事件并不激发. 当DocumentComplete 事件并未触发,  ProgressChange 事件被用来控制以检测某页是否完成加载. 载一个简单的web页或者没有嵌入帧时ProgressChange 工作的很好.

记住 ProgressChange 右两个参数告诉你下载操作的进度.第一个参数当下在完成时候设定为-1, 者可以帮助你检测是否可做类打印等操作

让我们学习一个打印控active控件,为从Internet Explorer接收事件,你必须设置事件接收,意味着你必须通过IWebBrowser2 接口以获得实现,如下实现:

protected:
   CComPtr<IWebBrowser2> m_spWebBrowser;

.

接下来覆盖IOleObjectImpl 的SetClientSite方法的实现. SetClientSite 放方法是在Internet Explorer通知气客户区的控件的时候被调用.你可用客户区的site指针 (m_spClientSite) 存取容器并且得到IWebBrowser2接口指针. 在SetClientSite 实现中, 你必须首先调用其基类版本,就想如下:

IOleObjectImpl<CPrintCtl>::SetClientSite(pClientSite);

这些带吗看起来可能有些生疏, 但记住 IOleObjectImpl是一个模版类. 为了调用它的方法, 你必须制定要求的模版参数以指示编译器哪一个类实例在调用SetClientSite 方法时被使用. 现在讲残存的访问容器和IWebBrowser2接口指针的代码从Print方法迁移到SetClientSite 方法Now move the remaining codePrint 方法将看起来如下:

STDMETHODIMP CPrintCtl::Print()
{
   ATLASSERT(m_spWebBrowser);
 
   HRESULT hr = E_FAIL;
 
   if (m_spWebBrowser)
   {
      hr = m_spWebBrowser->ExecWB(OLECMDID_PRINT, 
                                  OLECMDEXECOPT_PROMPTUSER, NULL, NULL);
   }
 
   return hr;
}

SetClientSite 方法将接收事件,SetClientSite 讲看起来如下:

注意


你不能够再FinalConstruct m方法中接收事件因为此时客户站点还未设定。

STDMETHODIMP CPrintCtl::SetClientSite(IOleClientSite* pClientSite)
{
   HRESULT hr = IOleObjectImpl<CPrintCtl>::SetClientSite(pClientSite);
 
   if (!pClientSite)
   {
      return hr;
   }
 
   CComPtr<IOleContainer> spContainer;
   m_spClientSite->GetContainer(&spContainer);
 
   ATLASSERT(spContainer);
 
   if (SUCCEEDED(hr))
   {
      // Set up the event sink.
      //
      CComQIPtr<IServiceProvider, &IID_IServiceProvider>
         spServiceProvider(spContainer);
 
      ATLASSERT(spServiceProvider);
 
      if (spServiceProvider)
      {
         spServiceProvider->QueryService(SID_SInternetExplorer,
                                         IID_IWebBrowser2,
                                         (void**)&m_spWebBrowser);
         ATLASSERT(m_spWebBrowser);
 
         if (m_spWebBrowser)
         {
            AtlAdvise(m_spWebBrowser, GetUnknown(),
                      DIID_DWebBrowserEvents2, &m_dwCookie);
         }
      }
   }
 
   return hr;
}

注意到在AtlAdvise 调用时你必须建立protected 或者privateDWORD的数据成员以掌握返回自AtlAdvise 方法的cookie. CprintCtl 类的构造函数初始化改成员为0.尽管我们注意到CPrintCtl::SetClientSite 方法使用IOleObjectImpl::SetClientSite 方法的返回值. 此方法并不检查已被调用的返回值因为CPrintCtl::SetClientSi将 反射客户站点的设定状态.

最好, 我们检查pClientSite 的返回值,输入参数是NULL. 如果这样,我们当Internet Explorer 卸载这些控时, 他调用SetClientSite w设置为NULL. 或者告诉你已经从站点解除, 所以包含一个接口,IWebBrowser2 容器不需要一定执行。.

因为当你完成任务时应当关闭任务的站点, 也包含某个控件被卸载时。检查pClientSite是否为NULL,以便放置AtlUnadvise 方法. 记住pClientSite在控件被卸载时为 NULL. 看起来如下:

if (!pClientSite)
{
   ATLASSERT(m_spWebBrowser);
 
   if (m_spWebBrowser)
      AtlUnadvise(m_spWebBrowser, DIID_DWebBrowserEvents2, m_dwCookie);
 
   return hr;
}

现在你可以使用AtlAdvise接收事件,让我们控制事件.为此你必须覆盖重写IDispatchImpl 的Invoke方法. 典型的,你将为你的时间建立一个单独的类因为 Internet Explorer 事件的DISPIDs 必须同你的控件的DISPIDs 不同.但在此你可以简单在CPrintCtl  类中来实现.实现Invoke (入代码所示)以控制ProgressChange 事件.在事件句柄, 如果progres的总数设定为-1,设定一个标志变量指示已被打印.

 

STDMETHODIMP CPrintCtl::Invoke(DISPID dispidMember, 
                               REFIID riid, 
                               LCID lcid,
                               WORD wFlags, 
                               DISPPARAMS* pDispParams, 
                               VARIANT* pvarResult, 
                               EXCEPINFO* pExcepInfo,
                               UINT* puArgErr)
{
   if (riid != IID_NULL)
      return DISP_E_UNKNOWNINTERFACE;
 
   if (!pDispParams)
      return DISP_E_PARAMNOTOPTIONAL;
 
   switch (dispidMember)
   {
      //
      // The parameters for this DISPID:
      // [0]: Maximum progress - VT_I4
      // [1]: Amount of total progress - VT_I4
      //
      case DISPID_PROGRESSCHANGE:
         if (pDispParams->cArgs != 0)
         {
            // Make sure that you access the
            // correct data member of the rgvarg array.
            // To do this, check the type of data to
            // make sure it is correct.
            //
            if (pDispParams->cArgs > 1
               && pDispParams->rgvarg[1].vt == VT_I4
               && pDispParams->rgvarg[0].vt == VT_I4)
            {
               if (-1 == pDispParams->rgvarg[1].lVal)
                  m_fCanBePrinted = TRUE;
            }
         }
 
         break;
 
      default:
         // Call the base class implementation of Invoke
         // so that IPrintCtl methods and properties will
         // work correctly.
         //
         IDispatchImpl<IPrintCtl, &IID_IPrintCtl, 
            &LIBID_ATLPRINTLib>::Invoke(dispidMember, riid, lcid,
                                        wFlags, pDispParams,
                                        pvarResult, pExcepInfo, puArgErr);
 
         break;
   }
 
   return S_OK;
}

ProgressChange 事件处理中,当Progress参数(pDispParams->rgvarg[1].lVal) 是-1, 我们设置一个变量告诉控件问打光在完成可以打印. FCanBePrinted 就是我们要设定的变量。

现在当用户试图调用Print 方法打印文档,你可以检查变量以确定是否可打印. 此处为Print 方法的代码:

STDMETHODIMP CPrintCtl::Print()
{
   if (!m_fCanBePrinted)
   {
      ::MessageBox(NULL, _T("The page is not ready to be printed."),
                   _T("PrintCtl"), MB_OK);
      return E_FAIL;
   }
 
   ATLASSERT(m_spWebBrowser);
 
   HRESULT hr = E_FAIL;
 
   if (m_spWebBrowser)
   {
      hr = m_spWebBrowser->ExecWB(OLECMDID_PRINT, 
                                  OLECMDEXECOPT_PROMPTUSER, NULL, NULL);
   }
 
   return hr;
}

 


  • 2
    点赞
  • 0
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值