Monday, January 10, 2005

 

IE HTML stream read/write

1.
浏览器控件教学:使用流加载和保存HTML内容

本文的部分内容翻译自MSDN文章Loading HTML content from a Stream
IPersist* 接口,以及它的附属方法,可以被用于Microsoft® Visual C++® 和 WebBrowser 控件使用流载入和保存HTML内容
本文讨论载入HTML内容需要的步骤,分为以下几部分:
定位到 about:blank
DHTML 对象模型的有效性
使用 QueryInterface 获得IPersist*接口
使用IPersist*接口载入和保存HTML内容
载入和保存HTML元素数据
已知问题
参考
相关主题
定位到 about:blank
IWebBrowser2 接口的IWebBrowser2::Navigate2 方法 使得你可以让浏览器定位(Navigate)到一个URL。在下面的示例代码中, IWebBrowser2::Navigate2 方法 被用于定位到 about:blank 页面. 定位到这个空的页面确保了MSHTML 被加载,并且动态 HTML (DHTML) 对象模型中的 HTML 元素有效.
本示例演示了如何让浏览器定位到一个空的页面。m_pBrowser 变量包含从WebBrowser 控件获得的 IWebBrowser2 接口指针。m_pBrowser->Navigate2( _T("about:blank"), NULL, NULL, NULL, NULL );
DHTML 对象模型的有效性
DHTML 对象模型 用于访问和操作HTML页面的内容,并且在页面装载之前不可用。你的应用程序通过处理WebBrowser 控件的DWebBrowserEvents2::DocumentComplete事件来判断一个页面是否被装载了。 这个事件可能被页面中的每个框架触发,并且在顶层文档载入完成时再触发一次。你可以通过比较事件传递的IDispatch 接口指针和WebBrowser 控件来判断DWebBrowserEvents2::DocumentComplete 事件是否是顶层框架的。
这个WebBrowser DWebBrowserEvents2::DocumentComplete 事件的示例处理代码演示如何判断事件是否是顶层框架的, (如果是,)这指明HTML页面载入完成. 本示例也演示如何从一个内存块——在这个场合是一个包含需要显示的HTML内容的字符串——创建流。void myObject::DocumentComplete(LPDISPATCH pDisp, VARIANT* URL)
{
HRESULT hr;
IUnknown* pUnkBrowser = NULL;
IUnknown* pUnkDisp = NULL;
IStream* pStream = NULL;
HGLOBAL hHTMLText;
static TCHAR szHTMLText[] = " 流测试。本HTML内容已经从流中加载。";
// 这个 DocumentComplete 事件是否是顶层框架窗口的?
// 检查 COM 标识: 比较IUnknown 接口指针.
hr = m_pBrowser->QueryInterface( IID_IUnknown, (void**)&pUnkBrowser );
if ( SUCCEEDED(hr) )
{
hr = pDisp->QueryInterface( IID_IUnknown, (void**)&pUnkDisp );
if ( SUCCEEDED(hr) )
{
if ( pUnkBrowser == pUnkDisp )
{ // 这是顶层框架窗口的DocumentComplete 事件 —— 页面 载入完成!
// 建立一个包含HTML内容的流
// 另外, 这个流可以是被传递过来的(而不是被创建的)

size_t = cchLength;
// TODO: 安全地判断 szHTMLText的长度,单位是TCHAR.
hHTMLText = GlobalAlloc( GPTR, cchLength+1 );

if ( hHTMLText )
{
size_t cchMax = 256;
StringCchCopy((TCHAR*)hHTMLText, cchMax + 1, szHTMLText);
// TODO: 在这里加入错误处理代码。
hr = CreateStreamOnHGlobal( hHTMLText, TRUE, &pStream );
if ( SUCCEEDED(hr) )
{
// 调用辅助函数让网络浏览器加载流。
LoadWebBrowserFromStream( m_pBrowser, pStream );
pStream->Release();
}
GlobalFree( hHTMLText );
}
}
pUnkDisp->Release();
}
pUnkBrowser->Release();
}
}
使用 QueryInterface 获得IPersis*等接口
WebBrowser 控件的IWebBrowser2::get_Document 属性返回表示顶层框架的DHTML 对象模型的文档对象。MSHTML 通过文档对象和其他HTML元素对象,例如Frame, IFrame等等实现的IPersistStreamInit,IPersistFile等接口提供使用流载入和保存HTML的功能。对象的IDispatch 接口可用于通过使用QueryInterface和IID_IPersistStreamInit 等接口标识查询相应接口指针,如下列代码示例所述.HRESULT LoadWebBrowserFromStream(IWebBrowser2* pWebBrowser, IStream* pStream)
{
HRESULT hr;
IDispatch* pHtmlDoc = NULL;
IPersistStreamInit* pPersistStreamInit = NULL;
// 返回文档对象.
hr = pWebBrowser->get_Document( &pHtmlDoc );
if ( SUCCEEDED(hr) )
{
// >查询 IPersistStreamInit接口
hr = pHtmlDoc->QueryInterface( IID_IPersistStreamInit, (void**)&pPersistStreamInit );
if ( SUCCEEDED(hr) )
{
// 初始化文档.
hr = pPersistStreamInit->InitNew();
if ( SUCCEEDED(hr) )
{
// 载入流内容
hr = pPersistStreamInit->Load( pStream );
}
pPersistStreamInit->Release();
}
}
}
使用IPersist*接口载入和保存HTML内容
IPersistStreamInit 接口具有用于从流初始化和载入HTML内容的InitNew 和Load 方法以及用于保存的Save方法。InitNew 方法初始化流到一个已知状态,Load 方法从流载入HTML内容,Save方法将HTML内容保存到流。
IPersistFile 接口具有用于从磁盘文件载入和保存HTML内容的Load 和Save方法。
在前面的示例代码中, HTML文档被初始化,并且HTML内容被从流中载入。
注意 从Microsoft Internet Explorer 5开始,多于一次调用 IPersist* 接口的Load 方法 是可行的。在更早的版本中,每个MSHTML的实例只支持一次Load 调用。
载入和保存HTML元素数据
如果HTML元素支持使用IPersistStorage, IPersistStreamInit, 或者 IPersistMemory,那么也可以通过类似的代码载入和保存信息。
对于网页中的ActiveX控件的信息的载入和保存,可以参考我的文章 如何: 通过HTML文档对象模型访问文档中的ActiveX控件的属性CSDN文档中心)来获得控件接口,然后查询ActiveX控件是否支持IPersist*接口。
注意:使用VB6.0编写的控件可能不支持这些接口,这时候需要使用IPersistPropertyBag或者属性集载入和保存信息。参见微软知识库文章Q272490 BUG: Visual Basic 组件的错误 0x800A02E0 "无法保存未初始化的类"
已知问题
微软知识库文章
Q271868 在Microsoft Internet Explorer (Programming) 5.5中,框架对象不支持IPersistStream, IPersistFile,IPersistMemory接口。
Q323569 BUG: PersistStreamInit::Load() 显示HTML内容为文本
Q264868 BUG:Internet Explorer 没有检测到内容类型从text/html到text/xml的改变。
参考
下列文章提供了关于组件对象模型(COM)的信息.
Inside OLE, 2nd Edition, by Kraig Brockschmidt (Microsoft Press)
Understanding ActiveX and OLE, by David Chappell (Microsoft Press)
Inside COM, by Dale Rogerson (Microsoft Press)
微软知识库文章
Q223337 信息: 使用Internet Explorer XML 解析器载入/保存XML数据
Q196340 如何: 获得HTML框架的WebBrowser 对象模型
相关主题
Microsoft Visual Studio
The Component Object Model Specification

2.
作者:Flier
出处:http://www.blogcn.com/user8/flier_lu/main.asp?id=1125200

趁周末想折腾一下嵌入ASP.NET的WinForm程序需要用到WebBrowser控件的HTML源码读写
就把以前的一些代码片断移值到C#下,顺便发个帖子备忘,呵呵

思路其实很简单,直接通过document.documentElement.outerHTML或者使用IPersistStreamInit接口直接对流进行处理前者我就不废话了,后者实现方法如下

首先是写入HTML到已初始化的WebBrowser控件,初始化可以通过Navigate("about:blank")完成,必须确保WebBrowser.Document != null,否则应该推迟到DocumentComplete事件再读写

UCOMIStream stream = null;

CreateStreamOnHGlobal(Marshal.StringToHGlobalUni(value), true, out stream);

if(stream != null)

{
IPersistStreamInit persistentStreamInit =
(IPersistStreamInit)WebBrowser.Document;

persistentStreamInit.InitNew();
persistentStreamInit.Load(stream);
persistentStreamInit = null;
}

UCOMIStream是COM中IStream的CLR版本,CreateStreamOnHGlobal函数从一个字符串的地址创建一个IStream供使用

[DllImport("ole32.dll", PreserveSig=false)]
static extern void CreateStreamOnHGlobal(IntPtr hGlobal,
Boolean fDeleteOnRelease, [Out] out UCOMIStream pStream);

然后就是通过IPersistStreamInit接口初始化并载入HTML源码,IPersistStreamInit接口CLR缺省没有导入,定义如下

[ComVisible(true), ComImport(), Guid("7FD52380-4E07-101B-AE2D-08002B2EC713"),
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPersistStreamInit
{
void GetClassID([In, Out] ref Guid pClassID);

[return: MarshalAs(UnmanagedType.I4)] [PreserveSig]
int IsDirty();

void Load([In, MarshalAs(UnmanagedType.Interface)] UCOMIStream pstm);
void Save([In, MarshalAs(UnmanagedType.Interface)] UCOMIStream pstm,
[In, MarshalAs(UnmanagedType.I4)] int fClearDirty);
void GetSizeMax([Out, MarshalAs(UnmanagedType.LPArray)] long pcbSize);
void InitNew();
}

读取HTML也是类似思路,将HTML源码写到一个IStream中,然后转换成字符串供C#代码使用,不过实现方式比较麻烦。比较简单的方法还是使用ole32.dll提供的函数重建流,但这需要预先设定流的长度,如

UCOMIStream stream = null;

CreateStreamOnHGlobal(Marshal.AllocHGlobal(4096), true, out stream);

IPersistStreamInit persistentStreamInit =
(IPersistStreamInit)WebBrowser.Document;

persistentStreamInit.Save(stream, 0);
persistentStreamInit = null;

IntPtr pStr;

GetHGlobalFromStream(stream, out pStr);

return Marshal.PtrToStringAnsi(pStr);

然后使用GetHGlobalFromStream函数和Marshal.PtrToStringAnsi将流转换为字符串,另外一种方法是自行实现一个支持IStream接口的类通过流的方式灵活完成读取操作,我比较喜欢这种

using(MemoryStream stream = new MemoryStream())

{
ComStreamAdapter adapter = new ComStreamAdapter(stream);

IPersistStreamInit persistentStreamInit =
(IPersistStreamInit)WebBrowser.Document;

persistentStreamInit.Save(adapter, 0);

stream.Seek(0, SeekOrigin.Begin);

using(StreamReader reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}

这里的ComStreamAdapter是一个使用了adapter模式的类,将普通的System.IO.Stream转换为IStream支持的类

public class ComStreamAdapter : UCOMIStream
{
private Stream _stream;



public ComStreamAdapter(Stream stream)
{
_stream = stream;
}

#region UCOMIStream Members

public void Commit(int grfCommitFlags)
{
}

public void Clone(out UCOMIStream ppstm)
{
ppstm = null;
}

public void CopyTo(UCOMIStream pstm, long cb, System.IntPtr pcbRead, Syste
m.IntPtr pcbWritten)
{
}

public void Revert()
{
}

public void LockRegion(long libOffset, long cb, int dwLockType)
{
}

public void UnlockRegion(long libOffset, long cb, int dwLockType)
{
}

public void Seek(long dlibMove, int dwOrigin, System.IntPtr plibNewPositio
n)
{
_stream.Seek(dlibMove, (SeekOrigin)dwOrigin);

if(plibNewPosition != IntPtr.Zero)
{
Marshal.WriteInt32(plibNewPosition, (int)_stream.Position);
}
}

public void Read(byte[] pv, int cb, System.IntPtr pcbRead)
{
int size = _stream.Read(pv, (int)_stream.Position, cb);

if(pcbRead != IntPtr.Zero)
{
Marshal.WriteInt32(pcbRead, size);
}
}

public void Write(byte[] pv, int cb, System.IntPtr pcbWritten)
{
_stream.Write(pv, 0, cb);

if(pcbWritten != IntPtr.Zero)
{
Marshal.WriteInt32(pcbWritten, cb);
}
}

public void SetSize(long libNewSize)


_stream.SetLength(libNewSize);
}

public void Stat(out STATSTG pstatstg, int grfStatFlag)
{
pstatstg = new STATSTG ();
}

#endregion
}

具体实现

[ComVisible(true), ComImport(), Guid("7FD52380-4E07-101B-AE2D-08002B2EC713",
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPersistStreamInit
{
void GetClassID([In, Out] ref Guid pClassID);

[return: MarshalAs(UnmanagedType.I4)] [PreserveSig]
int IsDirty();

void Load([In, MarshalAs(UnmanagedType.Interface)] UCOMIStream pstm);
void Save([In, MarshalAs(UnmanagedType.Interface)] UCOMIStream pstm,
[In, MarshalAs(UnmanagedType.I4)] int fClearDirty);
void GetSizeMax([Out, MarshalAs(UnmanagedType.LPArray)] long pcbSize);
void InitNew();
}

[DllImport("ole32.dll", PreserveSig=false)]
static extern void CreateStreamOnHGlobal(IntPtr hGlobal, Boolean fDeleteOnRelease,
[Out] out UCOMIStream pStream);

[DllImport("ole32.dll", PreserveSig=false)]
static extern void GetHGlobalFromStream(UCOMIStream pStream,
[Out] out IntPtr hGlobal);

private string _html = null;

public string HTML
{
get
{
if(WebBrowser.Document == null)
{
return _html;
}
else
{
#if false
UCOMIStream stream = null;

IntPtr pBuf = Marshal.AllocHGlobal(4096);
try
{
CreateStreamOnHGlobal(pBuf, true, out stream);

IPersistStreamInit persistentStreamInit =
(IPersistStreamInit)WebBrowser.Document;

persistentStreamInit.Save(stream, 0);
persistentStreamInit = null;

IntPtr pStr;

GetHGlobalFromStream(stream, out pStr);

return Marshal.PtrToStringAnsi(pStr);
}
finally
{
Marshal.FreeHGlobal(pBuf);
}
#else
using(MemoryStream stream = new MemoryStream())
{
ComStreamAdapter adapter = new ComStreamAdapter(stream);

IPersistStreamInit persistentStreamInit =
(IPersistStreamInit)WebBrowser.Document;

persistentStreamInit.Save(adapter, 0);

stream.Seek(0, SeekOrigin.Begin);

using(StreamReader reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
#endif
}
}
set
{
if(WebBrowser.Document == null)
{
_html = value;
}
else
{
UCOMIStream stream = null;

IntPtr pStr = Marshal.StringToHGlobalUni(value);
try
{
CreateStreamOnHGlobal(pStr, true, out stream);

if(stream != null)
{
IPersistStreamInit persistentStreamInit =
(IPersistStreamInit)WebBrowser.Document;

persistentStreamInit.InitNew();
persistentStreamInit.Load(stream);
persistentStreamInit = null;
}
}
finally
{
Marshal.FreeHGlobal(pStr);
}
}
}
}

3.
作者:lostall
出处:http://www.pcvc.net/category/content.asp?sendid=178

介绍如何用程序的方法获得WebBrowser控件中的HTML的源代码,并可以通过修改源代码内容来修改页面内容(注意:不是显示一个新的页面)。

首先要加入WebBrowser控件,加入控件的方面我就不说了。获得源代码方法有两种:

一、方法1(严格说,这个方法只不过是调用WebBrowser自己的菜单命令"查看源文件而已",并非我们所希望的)
关键代码:

#include "mshtmcid.h"
void CHtmlView::OnMethod1()
{
CWnd* pWnd = NULL;

CWnd* pWndShell = m_browser.GetWindow(GW_CHILD); // get the webbrowser window pointer

if (pWndShell)
{
pWnd = pWndShell->GetWindow(GW_CHILD); //get the child window pointer
}

if (pWnd != NULL)
{
WPARAM wParam = MAKEWPARAM(IDM_VIEWSOURCE, 1); //convert to unsigned 32 bit value and pass it to wparam
pWnd->SendMessage(WM_COMMAND, wParam, (LPARAM)this->m_hWnd); //cool send a message to retreive the source.
}
}

二、方法2

原理在于取得IPersistStreamInit接口指针,然后把网页写到IStream流中去。
关键代码:

#include "mshtml.h"
//在SourceView中填写HtmlView中网页的源程序
void CMainFrame::OnMethod2()
{
IHTMLDocument2 *pHTMLDocument=NULL;
IPersistStreamInit *pPSI=NULL;

IStream *pStream=NULL;
HGLOBAL hHTMLText;

if (!(pHTMLDocument = (IHTMLDocument2*)m_pHtmlView->m_browser.GetDocument()))
return;

if (FAILED(pHTMLDocument->QueryInterface(&pPSI)))
{
// pHTMLDocument->Release();
return;
}

hHTMLText = GlobalAlloc(GMEM_FIXED, MAX_SIZE);
CreateStreamOnHGlobal(hHTMLText, TRUE, &pStream);
pPSI->Save(pStream, FALSE);

// m_pSourceView->SetWindowText((char*)hHTMLText);

long nEditLength = m_pSourceView->GetEditCtrl().GetWindowTextLength();
m_pSourceView->GetEditCtrl().SetSel(0, nEditLength);
m_pSourceView->GetEditCtrl().ReplaceSel("");
char *pText = (char*)hHTMLText;
long lHtmlLength = strlen(pText);
CString str("");
long n = 0;
for (long i=0; i < lHtmlLength; i++)
{
if (*pText != 0x0d && *pText != 0x0a)
{
str += *pText;
pText++;
}
else
{
pText++;
if (*pText == 0x0a)
pText++;
str += "\r\n";
nEditLength = m_pSourceView->GetEditCtrl().GetWindowTextLength();
m_pSourceView->GetEditCtrl().SetSel(nEditLength, nEditLength);
m_pSourceView->GetEditCtrl().ReplaceSel(str);
str.Empty();
}
}

pStream->Release();
pPSI->Release();
// pHTMLDocument->Release();
}

三、修改HTML源代码以改变网页的显示

这部分比较有意思,可以当作是一个小的HTML编辑器,看看预演效果。特别的不是显示一个新文件,而是修改原来的HTML文件。
关键代码:

//根据SourceView里的HTML文本改变HtmlView里的显示
void CMainFrame::OnChangehtml()
{
IHTMLDocument2 *pHTMLDocument=NULL;
IPersistStreamInit *pPSI=NULL;

IStream *pStream=NULL;
HGLOBAL hHTMLText;

if (!(pHTMLDocument = (IHTMLDocument2*)m_pHtmlView->m_browser.GetDocument()))
return;

if (FAILED(pHTMLDocument->QueryInterface(&pPSI)))
{
// pHTMLDocument->Release();
return;
}

pHTMLDocument->clear();
pPSI->InitNew();

LPCTSTR strText = m_pSourceView->LockBuffer();
DWORD dwLength = strlen(strText);
hHTMLText = GlobalAlloc(GMEM_FIXED, dwLength);
memset(hHTMLText, 0, dwLength);
memcpy(hHTMLText, strText, dwLength);
m_pSourceView->UnlockBuffer();
CreateStreamOnHGlobal(hHTMLText, TRUE, &pStream);
ULARGE_INTEGER libNewSize;
libNewSize.QuadPart = dwLength;
pStream->SetSize(libNewSize);这一步必须要,否则显示时会有多余字符出现
pPSI->Load(pStream);

pStream->Release();
pPSI->Release();
// pHTMLDocument->Release();
}

有时侯不能显示出网页而显示的是源码文本,比如微软网站的首页就是这种情况。把源码中的这句话 < META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso8859-1" />去掉就可以了。原因不明。

(按:看到VC和C#的不同之处了吧。但是上述方法又一个问题,请看下面的帖子)

4.
作者:111222
出处:CSDN

网页内容倒是动态改变了,但你查看一下网页属性会发现,网页变成了:about:blank

很痛苦是吧,变成空白页了,呵呵~~

下面方法利用IHtmlDocument2的方法动态改变网页内容,而不改变网页属性

BOOL CXXXXView::put_bodyHtml(CString cs)
{
IHTMLDocument2* pHtmlDoc2 = (IHTMLDocument2*)GetHtmlDocument();
if( pHtmlDoc2)
{
HRESULT hr = S_OK;
IHTMLElement *pBodyElement;
hr=pHtmlDoc2->get_body( &pBodyElement);
if(pBodyElement!=NULL)
{
BSTR pbBody = cs.AllocSysString();
hr=pBodyElement->put_innerHTML(pbBody);
pBodyElement->Release();
}
pHtmlDoc2->Release();
if( hr==S_FALSE) return FALSE;
else return TRUE;
}
else return FALSE;

}

到时候你只需要这样调用:put_innerHtml("a string");

9/14/2004 补充
需要说明的是
Webbrowser里面至少要有一个文档(about:blank也可以)才能开始工作。可以这么写

HRESULT CApp::LoadURLFromMoniker()
{
//load m_szURL
//use m_pMSHTML
IPersistStreamInit *pPersist = NULL;
m_pMSHTML->QueryInterface(IID_IPersistStreamInit,
(LPVOID *) &pPersist);
if (pPersist)
{
pPersist->InitNew();
IStream* pStream;
HRESULT hr = NOERROR;
char szWebSite[] = "http://www.163.net";
(URLOpenBlockingStream( 0, szWebSite, &pStream, 0, 0));
pPersist->Load(pStream);
pPersist->Release();
}
return S_OK;
}

如果只是要修改/重写网页内容的话可以不用IStream,直接用IHTMLDocument2的write方法就可以了。但是这时IHTMLDocument2的URL属性就会丢掉了,再解释脚本以及取Links都会出现问题~~~~~~



<< Home

This page is powered by Blogger. Isn't yours?