1。分析网页上的元素
private
void
btn_Find_Click(
object
sender, EventArgs e)
{ // find all the input controls on the page if ( this .wb_Show.Document == null ) { MessageBox.Show( " 请先打开一个要分析的网页 " ); return ; } string type = this .cbx_type.Text.ToLower().Trim(); if (type == "" ) { MessageBox.Show( " 请选择一个分析对象的类型 " ); return ; } this .lb_InputList.Items.Clear(); HtmlDocument htm = this .wb_Show.Document; HtmlElementCollection all = htm.All; for ( int i = 0 ; i < all.Count; i ++ ) { HtmlElement elem = all[i]; if (elem.TagName.ToLower() == type) { this .lb_InputList.Items.Add(elem.Name); } } MessageBox.Show(" 总共找到 " + this .lb_InputList.Items.Count.ToString() + " 个符合条件的结果 " ); }
这一段代码是用来触发按钮事件的,注意使用的是click参数
if
(elem.Name.ToLower()
==
login.Attributes[
"
name
"
].Value)
{ // elem.InvokeMember("click"); elem.InvokeMember( " click " ); }
对于form的提交,得使用submit
else
{ XmlNode form = FindNode( " form " ); htm.Forms[form.Attributes[ " name " ].Value].InvokeMember( " submit " ); }
获取值,和赋值类似
XmlNode userName
=
FindNode(
"
userName
"
);
if
(elem.TagName.ToLower()
==
"
input
"
&&
elem.Name.ToLower()
==
userName.Attributes[
"
name
"
].Value)
{ elem.InnerText = userName.InnerText; }
可以使用这些简单的应用做一个网页自动登录之类的系统
2.
实现WebBrowser控件的HTML源代码读写 思路其实很简单,直接通过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; } UCOMIStream Members #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
}
3.一些应用 1:如何处理弹出新页面的事件(总是在我的浏览器里面现实新页面) 2:如何处理window.close事件,让我的浏览器页关闭 3:让html页面的js调用我的browse的函数 4:如何让我的browse调用html的js函数。 使用场景:一个web程序,让用户使用自定义浏览器来浏览,该web程序会调用浏览者机器上一些接口。 我的这个浏览器叫做AppBrowser。 关于ObjectForScripting 的介绍http://msdn2.microsoft.com/en-us/library/system.windows.forms.webbrowser.objectforscripting.aspx 首先,第一个问题。 如果只是放置一个browse在那里,在html中打开新页面的时候,他默认使用IE或者其他浏览器来打开网页。如果想要让我的browse也同时能处理所有的新开页面,就要增加一个对_NewWindow事件的处理。 private void wb_Container_NewWindow(object sender, CancelEventArgs e) { e.Cancel = true; AppBrowser newAB = new AppBrowser(wb_Container.Url.ToString()); newAB.Show(); } 在这里要注意的是 1:e.Cancel = true;是为了取消这个事件,不然又打开一个IE 2:wb_Container.Url.接受到的是新页面的参数 关于关闭浏览器 通常,如果设置了这样的js:window.close,那么,IE会自动关闭。但是我的browse却不会,至少默认的是如此的。 为了关闭我的浏览器,我需要接收这个函数。但是,很可惜,找了半天都没找到这个事件在那里处理,于是结合下一个问题,一下子解决了。(其实是半个解决,只有自己写的web程序才能处理)。 关于web调用我的浏览器的函数。 这就成了web和win的交互了,这个win就是在客户端的。以前交互的方式是写一个ActiveX控件,让web调用他,进而访问客户机器上的一些资源。现在的这种方式则是通过自己提供一个符合COM接口的自定义browse来实现。 1:我的browse必须是符合COM接口的[System.Runtime.InteropServices.ComVisibleAttribute(true)] 2:设置一个属性 this.wb_Container.ObjectForScripting = this 这样,Web中就可以这样调用了javascript:window.external.xxx('xx')。比方说上边的那个关闭窗口的调用就可以这样写: οnclick="javascript:window.external.close();" 这个调用,其实是调用的我的browse的Close函数。这个函数是我的winForm上默认的那一个函数。调用其他函数亦然,只要是公开方法就可以。 关于如何browse调用web页面中的函数。 第一个,可以通过直接调用页面中元素的方式来实现,在我上一篇里面有所介绍。 第二个,就是可以直接访问.Document.InvokeScript函数来实现。 比如: public object InvokeHtmlJsScript(string scriptName,object[] objects) { return this.wb_Container.Document.InvokeScript(scriptName, objects); }
简单的类
1 // set this class as a COM 2 [System.Runtime.InteropServices.ComVisibleAttribute( true )] 3 public partial class AppBrowser : Form 4 { 5 public AppBrowser() 6 { 7 InitializeComponent(); 8 } 9 /**/ /// <summary> 10 /// which the url will be go 11 /// </summary> 12 /// <param name="url"></param> 13 public AppBrowser( string url) 14 { 15 InitializeComponent();16 _url = url; 17 this .wb_Container.Navigate(_url); 18 this .wb_Container.ObjectForScripting = this ; // set this to be the COM handler 19 20 }21 private string _url; 22 public string Url 23 { 24 get { return this ._url; } 25 }26 /**/ /// <summary> 27 /// open new page in the window which is also in my brower but not in IE 28 /// </summary> 29 /// <param name="sender"></param> 30 /// <param name="e"></param> 31 private void wb_Container_NewWindow( object sender, CancelEventArgs e) 32 { 33 e.Cancel = true ; 34 AppBrowser newAB = new AppBrowser(wb_Container.Url.ToString()); 35 newAB.Show();36 }37 /**/ /// <summary> 38 /// this function can be invoked by js in html 39 /// like this 'javascript:window.external.ShowMessage('this is invoke from web');' 40 /// </summary> 41 /// <param name="msg"></param> 42 public void ShowMessage( string msg) 43 { 44 MessageBox.Show(msg);45 }46 /**/ /// <summary> 47 /// can invoke script in the html showing in the webbrowser 48 /// </summary> 49 /// <param name="scriptName"></param> 50 /// <param name="objects"></param> 51 /// <returns></returns> 52 public object InvokeHtmlJsScript( string scriptName, object [] objects) 53 { 54 return this .wb_Container.Document.InvokeScript(scriptName, objects); 55 }56 }
后记:经过测试,终于找到了一种方法可以解决window.close的问题了(第二个问题)
1
private
void
wb_Container_DocumentCompleted(
object
sender, WebBrowserDocumentCompletedEventArgs e)
2
{ 3 wb_Container.Document.Window.Unload += new HtmlElementEventHandler(Window_Unload); 4 }
5
6
void
Window_Unload(
object
sender, HtmlElementEventArgs e)
7
{ 8 if ( this .wb_Container.Document == null ) 9 this .Close(); 10 }
原理: 1:代理window的unload事件。这个事件在页面卸载的时候触发。 2:在这个事件之后检查webbrowser的值。如果是window.close,那么属性为空。 可能这个方法还是不够好,但是现下可用了。