2006-12-13

Delphi - 直接访问WebBrowser控件中的html源码


实现在自己的程序中显示HTML文档,我们一般采用IE(Internet
Explorer本文中简称为IE)发行时附带的一个ActiveX控件TWebBrowser。这个控件使用和IE相同的内核,功能强大,并从
Delphi5开始,正式得到Inprise公司的支持,取代了原来的那个THTML控件,成为Delphi中显示HTML文档的首选控件。 />
是在实际编程过程中,我发现这个控件提供的功能有很多限制,比如对HTML文档的浏览,只能通过指定URL或文件名来实现,不能像以往使用THTML控件
那样直接读写HTML源码。因此如果程序动态生成了一段HTML文本,就必须把文本内容先写到一个临时文件,然后再将此文件的文件名传递给
WebBrowser控件,实现显示。走这一个弯路使程序响应速度受到很大影响,而且容易遗留下一些"垃圾"(临时文件)。 /> 其实,WebBrowser控件中的Document对象,这个对象提供了一个IPersistStreamInit接口,通过此接口,我们可以方便地实现对HTML源码的读写。

以下是IPersistStreamInit接口的相关定义及说明:

{ IPersistStream interface }

{$EXTERNALSYM IPersistStream}

IPersistStream = interface(IPersist)

['{00000109-0000-0000-C000-000000000046}']

function IsDirty: HResult; stdcall; />// 最后一次存盘后是否被修改

function Load(const stm: IStream): HResult; stdcall;

// 从流中载入

function Save(const stm: IStream;

fClearDirty: BOOL): HResult; stdcall;

// 保存到流

function GetSizeMax(out cbSize: Largeint):

HResult; stdcall; // 取得保存所需空间大小

end;

{ IPersistStreamInit interface }

{$EXTERNALSYM IPersistStreamInit}

IPersistStreamInit = interface(IPersistStream)

['{7FD52380-4E07-101B-AE2D-08002B2EC713}']

function InitNew: HResult; stdcall; // 初始化

end;

首先来实现写,因为这是最迫切的要求:

procedure SetHtml(const WebBrowser:

TWebBrowser; const Html: string);

var

Stream: IStream;

hHTMLText: HGLOBAL;

psi: IPersistStreamInit;

begin

if not Assigned(WebBrowser.Document) then Exit;

hHTMLText := GlobalAlloc(GPTR, Length(Html) + 1);

if 0 = hHTMLText then RaiseLastWin32Error;

CopyMemory(Pointer(hHTMLText),

PChar(Html), Length(Html));

OleCheck(CreateStreamOnHGlobal

(hHTMLText, True, Stream));

try

OleCheck(WebBrowser.Document.

QueryInterface(IPersistStreamInit, psi));

try

OleCheck(psi.InitNew);

OleCheck(psi.Load(Stream));

finally

psi := nil;

end;

finally

Stream := nil;

end;

end;


首先,此过程需要的两个参数,WebBrowser是显示目的控件,Html是需要显示的HTML源码;然后,先检查
WebBrowser.Document对象是否有效,无效则退出;接着在系统全局堆里分配一块内存,将需要显示的HTML源码复制进去。这是因为下一步
需要建立一个WebBrowser控件可以读取的流。GlobalAlloc函数的参数GPTR表示需要分配一块固定的以0初始化过的内存区域,如果分配
失败则返回0,则通过RaiseLastWin32Error函数引发一个异常,提示用户;然后用CreateStreamOnHGlobal函数建立一
个基于全局堆内存块的流,第二个参数如果为True则流在释放时自动释放所占全局堆内存。如果建立成功则此流和刚刚建立的内存块共用同一块内存区域。接着
用WebBrowser.Document.QueryInterface函数建立一个IPersistStreamInit接口。然后就可以直接使用此
接口,psi.InitNew初始化状态;psi.Load(Stream)从流中载入HTML源码。 /> 至此,以Html参数指定的HTML源码就在WebBrowser参数指定的控件中显示出来。 />
得注意的是,每个关于COM接口的函数调用,也就是那些返回类型为HResult的函数,都必须以OleCheck包装,因为一个不检查返回状态的COM
接口操作实在太危险了;此外接口的释放,虽然Delphi可以在后台自动完成,但作为一个好的编程习惯,还是应该显式地手工释放,释放只需将接口设为
nil即可。 />接着来实现HTML源码的读:

function GetHtml(const WebBrowser:

TWebBrowser): string;

const

BufSize = $10000;

var

Size: Int64;

Stream: IStream;

hHTMLText: HGLOBAL;

psi: IPersistStreamInit;

begin

if not Assigned(WebBrowser.Document) then Exit;

OleCheck(WebBrowser.Document.QueryInterface

(IPersistStreamInit, psi));

try

//OleCheck(psi.GetSizeMax(Size));

hHTMLText := GlobalAlloc(GPTR, BufSize);

if 0 = hHTMLText then RaiseLastWin32Error;

OleCheck(CreateStreamOnHGlobal(hHTMLText,

True, Stream));

try

OleCheck(psi.Save(Stream, False));

Size := StrLen(PChar(hHTMLText));

SetLength(Result, Size);

CopyMemory(PChar(Result), Pointer(hHTMLText),

Size);

finally

Stream := nil;

end;

finally

psi := nil;

end;

end;


此函数有一个参数WebBrowser指定从那个控件读取HTML源码,返回一个字符串为此控件中的HTML源码。首先还是要先检查
WebBrowser.Document对象是否有效,无效则退出;然后取得IPersistStreamInit接口;接着取得HTML源码的大小:本
来应该使用IPersistStreamInit接口的GetSizeMax函数,但在我的机器上测试,这个函数范围值衡为0,无效。因此只能先定义一个
足够大的缓冲区,如BufSize =
$10000字节(注意此缓冲区应该足够大);然后同样地分配全局堆内存块,建立流,然后将HTML文本写到流中。因为此HTML文本在流中是以#0结尾
的字符串,因此可以用Size := StrLen(PChar(hHTMLText))取得实际长度,用SetLength(Result,
Size);设置返回字符串长度为HTML源码实际长度,最后复制字符串到返回字符串中。 /> 至此,直接访问WebBrowser控件中的HTML源码所需的两个函数全部解析完毕。 /> 过需要注意的时,在使用这两个函数前,最好对WebBrowser.Document对象进行初始化。下面提供一个函数,通过显示一个空白页面实现WebBrowser.Document对象初始化。

procedure ShowBlankPage(WebBrowser:

TWebBrowser);

var

URL: OleVariant;

begin

URL := 'about:blank';

WebBrowser.Navigate2(URL);

end;

在你有WebBrowser控件的Form的FormCreate事件里调用此函数,初始化WebBrowser.Document对象。

精选文章

四家人天天同吃同乐,这个小集体成了一家人 | 南方周末

四家人天天同吃同乐,这个小集体成了一家人 | 南方周末 深秋的广州阳光明媚,碧空万里,适合外出走走、看看。近日,我联系上了一位久违的朋友阿乐。阿乐是地道的番禺人,家的准确位置是st街道st村,离我家不远,驱车半小时就到了。按照他发我的定位,我到了一处小山坡。 我以为这里是他家,但...