話說有了WebBrowser類
終於不用自己手動封裝SHDocVw的AxWebBrowser這個ActiveX控件了
這個類如果僅僅作為一個和IE一模一樣浏覽器
那就太沒意思了(還不如直接用IE呢)
那麼
無論我們是想做一個
定制版IE
還是希望利用HTML來做用戶界面(指WinApp而非WebApp
許多單機軟件
包括Windows的幫助支持中心
都是HTML做的)
都少不了Windows Form和包含在WebBrowser中的Web頁面的交互
本文將通過幾個實際的例子
初步介紹一下WinForm和WebBrowser所包含的Web頁面之間的交互
下面的代碼假設你已經建立了一個Windows Form
上面有一個WebBrowser名為
webBrowser
Study Case
用WinForm的Event Handler響應Web頁面的事件
現在有這樣一個Windows Application
它的界面上只有一個WebBrowser
顯示一個本地的HTML文件作為界面
現在的問題是
所有邏輯都可以放在HTML文件裡
唯獨
關閉
按鈕遇到了困難——通常
Web頁面是沒有辦法直接控制浏覽器的
更不用說結束這個WinForm程序了
但是
在
Net
當中
由Windows Form響應Web頁面的事件
已經成為了現實
在
Net
中
整個HTML文檔以及其包含的各個HTML元素
都和一個個HtmlDocument
HtmlElement之類的
Net對象對應
因此只要找到這個
關閉
按鈕對應的HtmlElement對象
為其click事件添加Event Handler即可
假設HTML源代碼如下
body>
html>
那麼找出該按鈕並為之添加Event Handler的代碼如下
HtmlDocument htmlDoc = webBrowser
Document;
HtmlElement btnElement = htmlDoc
All[
btnClose
];
if (btnElement != null)
{
btnElement
click += new HtmlElementEventHandler(HtmlBtnClose_Click);
}
其中HtmlBtnClose_Click是按下Web按鈕時的Event Handler
很簡單吧?那麼稍稍高級一點的——我們都知道一個HTML元素可能有很多各種各樣的事件
而HtmlElement這個類只給出最常用
共通的幾個
那麼
如何響應其他事件呢?這也很簡單
只需要調用HtmlElement的AttachEventHandler就可以了
btnElement
AttachEventHandler(
onclick
new EventHandler(HtmlBtnClose_Click));
//這一句等價於上面的btnElement
click += new HtmlElementEventHandler(HtmlBtnClose_Click);
把
onclick
換成其他事件的名字就可以了
例如
formElement
AttachEventHandler(
onsubmit
new EventHandler(HtmlForm_Submit));
Study Case
表單(form)的自動填寫和提交
要使我們的WebBrowser具有自動填表
甚至自動提交的功能
並不困難
假設有一個最簡單的登錄頁面
輸入用戶名密碼
點
登錄
按鈕即可登錄
已知用戶名輸入框的id(或Name
下同)是username
密碼輸入框的id是password
登錄
按鈕的id是submitbutton
那麼我們只需要在webBrowser的DocumentCompleted事件中使用下面的代碼即可
HtmlElement btnSubmit = webBrowser
Document
All[
submitbutton
];
HtmlElement tbUserid = webBrowser
Document
All[
username
];
HtmlElement tbPasswd = webBrowser
Document
All[
password
];
if (tbUserid == null || tbPasswd == null || btnSubmit == null)
return;
tbUserid
SetAttribute(
value
smalldust
);
tbPasswd
SetAttribute(
value
);
btnSubmit
InvokeMember(
click
);
這裡我們用SetAttribute來設置文本框的
value
屬性
用InvokeMember來調用了按鈕的
click
方法
因為不同的Html元素
其擁有的屬性和方法也不盡相同
所以
Net
提供了統一的HtmlElement來概括各種Html元素的同時
提供了這兩個方法以調用元素特有的功能
關於各種Html元素的屬性和方法一覽
可以查閱MSDN的DHTML Reference
※關於表單的提交
的確還有另一種方法就是獲取form元素而不是button
並用form元素的submit方法
HtmlElement formLogin = webBrowser
Document
Forms[
loginForm
];
//……
formLogin
InvokeMember(
submit
);
本文之所以沒有推薦這種方法
是因為現在的網頁
很多都在submit按鈕上添加onclick事件
以對提交的內容做最基本的驗證
如果直接使用form的submit方法
這些驗證代碼就得不到執行
有可能會引起錯誤
Study Case
查找並選擇文本
這次我們希望實現一個和IE一模一樣的查找功能
以對Web頁面內的文字進行查找
文本查找要借助於TextRange對象的findText方法
但是
Net裡並沒有這個對象
這是因為
Net
提供的HtmlDocument
HtmlWindow
HtmlElement等類
只不過是對原有mshtml這個COM組件的不完整封裝
只提供了mshtml的部分功能
所以許多時候
我們仍舊要借助mshtml來實現我們需要的功能
好在這些
Net類都提供了DomDocument這個屬性
使得我們很容易把
Net對象轉換為COM對象使用
下面的代碼演示了如何查找Web頁面的文本
(需要添加mshtml的引用
並加上using mshtml;)
public partial class SearchDemo : Form
{
// 建立一個查找用的TextRange(IHTMLTxtRange接口)
private IHTMLTxtRange searchRange = null;
public SearchDemo()
{
InitializeComponent();
}
private void btnSearch_Click(object sender
EventArgs e)
{
// Document的DomDocument屬性
就是該對象內部的COM對象
IHTMLDocument
document = (IHTMLDocument
)webBrowser
Document
DomDocument;
string keyword = txtKeyword
Text
Trim();
if (keyword ==
)
return;
// IE的查找邏輯就是
如果有選區
就從當前選區開頭+
字符處開始查找
沒有的話就從頁面最初開始查找
// 這個邏輯其實是有點不大恰當的
我們這裡不用管
和IE一致即可
if (document
selection
type
ToLower() !=
none
)
{
searchRange = (IHTMLTxtRange)document
selection
createRange();
llapse(true);
searchRange
moveStart(
character
);
}
else
{
IHTMLBodyElement body = (IHTMLBodyElement)document
body;
searchRange = (IHTMLTxtRange)body
createTextRange();
}
// 如果找到了
就選取(高亮顯示)該關鍵字
否則彈出消息
if (searchRange
findText(keyword
))
{
searchRange
select();
}
else
{
MessageBox
Show(
已搜索到文檔結尾
);
}
}
}
到此為止
簡單的查找就搞定了
至於替換功能
看了下一個例子
我相信你就可以觸類旁通輕松搞定了
Study Case
高亮顯示
上一個例子中我們學會了查找文本——究跟到底
對Web頁面還是只讀不寫
那麼
如果說要把所有的搜索結果高亮顯示呢?我們很快會想到把所有匹配的文字顏色
背景改一下就可以了
首先想到的可能是直接修改HTML文本吧……但是
與SourceCode的高亮顯示不同
我們需要並且只需要高亮頁面中的文本部分
HTML標簽
腳本代碼等等是絕對不應該去改動的
因此我們不能把整個頁面的Source Code讀進來然後replace
那樣有破壞HTML文件結構的可能
我們只能在能夠分離出文本與其他內容(標簽
腳本……)的前提下進行
具體方法有很多
下面提供兩個比較簡單的方法
方法一
使用TextRange(IHTMLTxtRange)
有了上一個Case的基礎
相信大家立刻會想到使用TextRange
沒錯
TextRange除了提供查找方法之外
還提供了一個pasteHTML方法
以指定的HTML文本替換當前TextRange中的內容
代碼片斷如下
public partial class HilightDemo : Form
{
// 定義高亮顯示效果的標簽
string tagBefore =
;
string tagAfter =
;
// ……
private void btnHilight_Click(object sender
EventArgs e)
{
HtmlDocument htmlDoc = webBrowser
Document;
string keyword = txtKeyword
Text
Trim();
if (keyword ==
)
return;
object oTextRange = htmlDoc
Body
InvokeMember(
createTextRange
);
mshtml
IHTMLTxtRange txtrange = oTextRange as mshtml
IHTMLTxtRange;
while (txtrange
findText(keyword
))
{
try
{
txtrange
pasteHTML(tagBefore + keyword + tagAfter);
}
catch { }
llapse(false);
}
}
}
※這段代碼裡獲取IHTMLTxtRange的方式和上面的例子稍稍不同
其實所謂條條大路通羅馬
本質是一樣的
方法二
使用DOM(文檔對象模型)
將HTML文檔解析為DOM
然後遍歷每個節點
在其中搜索關鍵字並進行相應替換處理即可
public partial class HilightDemo : Form
{
//……
private void btnHilight_Click(object sender
EventArgs e)
{
HTMLDocument document = (HTMLDocument)webBrowser
Document
DomDocument;
IHTMLDOMNode bodyNode = (IHTMLDOMNode)webBrowser
Document
Body
DomElement;
string keyword = txtKeyword
Text
Trim();
if (keyword ==
)
return;
HilightText(document
bodyNode
keyword);
}
private void HilightText(HTMLDocument document
IHTMLDOMNode node
string keyword)
{
// nodeType =
text節點
if (node
nodeType ==
)
{
string nodeText = node
nodeValue
ToString();
// 如果找到了關鍵字
if (nodeText
Contains(keyword))
{
IHTMLDOMNode parentNode = node
parentNode;
// 將關鍵字作為分隔符
將文本分離
並逐個添加到原text節點的父節點
string[] result = nodeText
Split(new string[] { keyword }
StringSplitOptions
None);
for (int i =
; i < result
Length
; i++)
{
if (result[i] !=
)
{
IHTMLDOMNode txtNode = document
createTextNode(result[i]);
parentNode
insertBefore(txtNode
node);
}
IHTMLDOMNode orgNode = document
createTextNode(keyword);
IHTMLDOMNode hilightedNode = (IHTMLDOMNode)document
createElement(
SPAN
);
IHTMLStyle style = ((IHTMLElement)hilightedNode)
style;
lor =
black
;
style
backgroundColor =
yellow
;
hilightedNode
appendChild(orgNode);
parentNode
insertBefore(hilightedNode
node);
}
if (result[result
Length
] !=
)
{
IHTMLDOMNode postNode = document
createTextNode(result[result
Length
]);
parentNode
insertBefore(postNode
node);
}
parentNode
removeChild(node);
} // End of nodeText
Contains(keyword)
}
else
{
// 如果不是text節點
則遞歸搜索其子節點
IHTMLDOMChildrenCollection childNodes = node
childNodes as IHTMLDOMChildrenCollection;
foreach (IHTMLDOMNode n in childNodes)
{
HilightText(document
n
keyword);
}
}
}
}
上面的兩段代碼都是為了清晰易懂而精簡得不能再簡的
有很多地方很不完善
比如
沒考慮到如何從高亮顯示狀態復原
也沒有大小寫匹配等等
當然
掌握了原理之後相信這些都不會太難
這兩種方法各有優缺點
使用TextRange較輕量迅速
而且有一個特長
就是可以把跨標簽(Tag)的關鍵字挑出來
例如
有這麼一段HTML
Helb>lo World!
先不管作者出於什麼目的讓Hel三個字母成為粗體總之顯示在頁面上的是一句Hello World!在我們希望高亮頁面中的Hello這個關鍵字時如果用DOM分析的話會得出含有Hel的節點和文本節點lo World!兩個節點因此無法將其挑出來而TextRange則能正確識別將其設置為高亮因此也可以說TextRange是只和文本有關和HTML語法結構無關的對象
但是TextRange也有其致命缺點加亮容易反向的話就很難換句話說去除高亮顯示的時候不能再用TextRange而需要采用其他方法
而DOM方法則正好相反 由於DOM的樹狀結構特性雖然不能(或者很難)跨越Tag搜索關鍵字但是去除高亮顯示並不繁瑣
Study Case 與腳本的互操作
在Case 當中我們已經看到Web頁面的HTML元素的事件可以由Windows Form端來響應可以在某種程度上看作是Web頁面調用WinForm那麼反過來WinForm除了可以直接訪問Web頁面的HTML元素之外能否調用Web頁面裡的各種Script呢?
首先是調用Web頁面的腳本中已經定義好的函數假設HTML中有如下Javascript
function DoAdd(a b) {
return a + b;
}
那麼我們要在WinForm調用它只需如下代碼即可
object oSum = webBrowserDocumentInvokeScript(DoAdd new object[] { });
int sum = ConvertToInt(oSum);
其次如果我們想執行一段Web頁面中原本沒有的腳本該怎麼做呢?這次Net的類沒有提供看來還要依靠COM了IHTMLWindow可以將任意的字符串作為腳本代碼來執行
string scriptline = @function ShowPageInfo() {;
string scriptline = @ var numLinks = documentlinkslength; ;
string scriptline = @ var numForms = documentformslength; ;
string scriptline = @ var numImages = documentimageslength; ;
string scriptline = @ var numScripts = documentscriptslength; ;
string scriptline = @ alert(網頁的統計結果\r\n鏈接數 + numLinks + ;
string scriptline = @ \r\n表單數 + numForms + ;
string scriptline = @ \r\n圖像數 + numImages + ;
string scriptline = @ \r\n腳本數 + numScripts);};
string scriptline = @ShowPageInfo();;
string strScript = scriptline + scriptline + scriptline + scriptline + scriptline +
scriptline + scriptline + scriptline + scriptline + scriptline;
IHTMLWindow win = (IHTMLWindow)webBrowserDocumentWindowDomWindow;
winexecScript(strScript Javascript);
From:http://tw.wingwit.com/Article/program/net/201311/12600.html