-
前言
在 Window Form 應用程序如果需要瀏覽網頁時可以崁入 WebBrowser 控件,但如果需要操作崁入的 HTML 的網頁元素,就需要額外的操作,以下紀錄幾種操作 HTML 元素的方法以及會碰到的問題。
建立 WinForm 應用程序
首先先建立一個 Window Form 應用程序,在 Form1 窗體上拉入一個 Button 與 WebBrowser 控件,如下
在 Button1 事件中使用 WebBrowser.Navigate 方法加載指定的網頁,如下
1.
private
void
button1_Click(
object
sender, EventArgs e)
2.
{
3.
webBrowser1.Navigate(Application.StartupPath +
"\\Page.html"
);
4.
}
直接執行程序后點擊「前往網頁」按鈕就能夠看到網頁已經加載 WebBrowser 控件中,如下
操作 HTML 網頁中的元素
接下來如需要針對 HTML 網頁上的元素進行控制時,可以透過 WebBrowser 控件提供的方法來處理,首先要在網頁加載完成后才進行操作,點選 WebBrowser 控件加入 DocumentCompleted 事件,DocumentCompleted 事件是當網頁文件完全加載后即會觸發,就可以透過 Document、DocumentText、或 DocumentStream 屬性取得網頁內容,如下。
01.
private
void
webBrowser1_DocumentCompleted
02.
(
object
sender, WebBrowserDocumentCompletedEventArgs e)
03.
{
04.
if
(webBrowser1.ReadyState == WebBrowserReadyState.Complete)
05.
{
06.
ThreadPool.QueueUserWorkItem(o =>
07.
{
08.
FormWork();
09.
});
10.
}
11.
}
12.
13.
private
void
FormWork()
14.
{
15.
// 進行操作
16.
}
透過 WebBrowser.ReadyState 屬性可以知道網頁使否已經加載完成,當加載完成后就使用 Thread 委派 FormWork 方法來處理網頁元素的操作,相關操作方法以下列出幾項。
操作文字框
1.
webBrowser1.Document.GetElementById(
"name"
).SetAttribute(
"value"
,
"Arvin"
);
操作下拉選單
1.
HtmlElementCollection opts = webBrowser1.Document.
2.
GetElementById(
"sex"
).GetElementsByTagName(
"option"
);
3.
foreach
(HtmlElement opt
in
opts)
4.
{
5.
if
(opt.GetAttribute(
"value"
) ==
"男"
)
6.
opt.SetAttribute(
"selected"
,
"selected"
);
7.
}
操作單選框
1.
HtmlElementCollection opts = webBrowser1.Document.
2.
GetElementsByTagName(
"input"
).GetElementsByName(
"skill"
);
3.
foreach
(HtmlElement opt
in
opts)
4.
{
5.
if
(opt.GetAttribute(
"value"
) ==
"WebForm"
)
6.
opt.InvokeMember(
"click"
);
7.
}
執行 JavaScript 方法 (不需寫入左右括號符號)
1.
webBrowser1.Document.InvokeScript(
"ShowInfo"
);
取得網頁內崁 iFrame 網頁的內容方法
1.
webBrowser1.Document.Window.Frames[0].Document
以上列出幾種操作網頁元素的做法,不外乎就是取得元素對象再透過 SetAttribute 方法來取得或設定值,最后將幾種操作方式組合后來測試自動輸入窗體的功能並且希望輸入窗體時能夠一步一步輸入,所以加入 Sleep 方法停頓一秒鍾,如下
01.
private
void
FormWork()
02.
{
03.
webBrowser1.Document.GetElementById(
"name"
).SetAttribute(
"value"
,
"Arvin"
);
04.
Thread.Sleep(1000);
05.
06.
webBrowser1.Document.GetElementById(
"phone"
).SetAttribute(
"value"
,
"0912345678"
);
07.
Thread.Sleep(1000);
08.
09.
HtmlElementCollection opts = webBrowser1.Document.
10.
GetElementById(
"sex"
).GetElementsByTagName(
"option"
);
11.
foreach
(HtmlElement opt
in
opts)
12.
{
13.
if
(opt.GetAttribute(
"value"
) ==
"男"
)
14.
opt.SetAttribute(
"selected"
,
"selected"
);
15.
}
16.
Thread.Sleep(1000);
17.
18.
HtmlElementCollection opts2 = webBrowser1.Document.
19.
GetElementsByTagName(
"input"
).GetElementsByName(
"skill"
);
20.
foreach
(HtmlElement opt
in
opts2)
21.
{
22.
if
(opt.GetAttribute(
"value"
) ==
"WebForm"
)
23.
opt.InvokeMember(
"click"
);
24.
}
25.
Thread.Sleep(1000);
26.
27.
webBrowser1.Document.InvokeScript(
"ShowInfo"
);
28.
}
完成后執行程序代碼上就會跳出 InvalidCastException 錯誤,如下
其原因是因為執行緒安全的關系,無法在非主執行緒的線程下操作 UI 控件,在以上的程序代碼中直接在 Thread 方法中取得 webBrowser1 控件進行操作因而導致了錯誤發生。
調整程序代碼先使用 Control.InvokeRequired 屬性來判斷是否在主執行緒下執行,若不是的話則呼叫 Invoke 方法指定委派,如下。
01.
private
delegate
void
FormWorkDelegate();
02.
03.
private
void
FormWork()
04.
{
05.
if
(webBrowser1.InvokeRequired)
06.
webBrowser1.Invoke(
new
FormWorkDelegate(FormWork));
07.
else
08.
{
09.
webBrowser1.Document.GetElementById(
"name"
).SetAttribute(
"value"
,
"Arvin"
);
10.
Thread.Sleep(1000);
11.
12.
webBrowser1.Document.GetElementById(
"phone"
).SetAttribute(
"value"
,
"0912345678"
);
13.
Thread.Sleep(1000);
14.
15.
HtmlElementCollection opts = webBrowser1.Document.
16.
GetElementById(
"sex"
).GetElementsByTagName(
"option"
);
17.
foreach
(HtmlElement opt
in
opts)
18.
{
19.
if
(opt.GetAttribute(
"value"
) ==
"男"
)
20.
opt.SetAttribute(
"selected"
,
"selected"
);
21.
}
22.
Thread.Sleep(1000);
23.
24.
HtmlElementCollection opts2 = webBrowser1.Document.
25.
GetElementsByTagName(
"input"
).GetElementsByName(
"skill"
);
26.
foreach
(HtmlElement opt
in
opts2)
27.
{
28.
if
(opt.GetAttribute(
"value"
) ==
"WebForm"
)
29.
opt.InvokeMember(
"click"
);
30.
}
31.
Thread.Sleep(1000);
32.
33.
webBrowser1.Document.InvokeScript(
"ShowInfo"
);
34.
}
35.
}
在次執行后發現不會跳出錯誤訊息了,但是卻發生另一個問題,就是當頁面加載后在填入窗體值時程序會停頓一段時候才一次顯示所以字段的值,這樣的結果並不符合當初所要一步一步的填入窗體的需求,如下
這種問題在我此篇MSDN發問中 ThankfulHeart 大有提到,Invoke 方法是使用 UI 的線程,而如果在 UI 線程中使用了 Sleep 方法將導致 UI 畫面被阻塞,因此才讓畫面陷入了停頓的狀態。
而在 FormWork 方法中我將操作的方法都包含在 Invoke 的程序區塊中,所以在此如要避免長時間的畫面阻塞,應該要盡可能的切割使用到 Invoke 的區段,因此修改程序如下。
01.
private
void
FormWork()
02.
{
03.
this
.Invoke(
new
MethodInvoker(() =>
04.
{
05.
webBrowser1.Document.GetElementById(
"name"
).SetAttribute(
"value"
,
"Arvin"
);
06.
}));
07.
Thread.Sleep(1000);
08.
09.
this
.Invoke(
new
MethodInvoker(() =>
10.
{
11.
webBrowser1.Document.GetElementById(
"phone"
).SetAttribute(
"value"
,
"0912345678"
);
12.
}));
13.
Thread.Sleep(1000);
14.
15.
this
.Invoke(
new
MethodInvoker(() =>
16.
{
17.
HtmlElementCollection opts = webBrowser1.Document.
18.
GetElementById(
"sex"
).GetElementsByTagName(
"option"
);
19.
foreach
(HtmlElement opt
in
opts)
20.
{
21.
if
(opt.GetAttribute(
"value"
) ==
"男"
)
22.
opt.SetAttribute(
"selected"
,
"selected"
);
23.
}
24.
}));
25.
Thread.Sleep(1000);
26.
27.
this
.Invoke(
new
MethodInvoker(() =>
28.
{
29.
HtmlElementCollection opts2 = webBrowser1.Document.
30.
GetElementsByTagName(
"input"
).GetElementsByName(
"skill"
);
31.
foreach
(HtmlElement opt
in
opts2)
32.
{
33.
if
(opt.GetAttribute(
"value"
) ==
"WebForm"
)
34.
opt.InvokeMember(
"click"
);
35.
}
36.
}));
37.
Thread.Sleep(1000);
38.
39.
this
.Invoke(
new
MethodInvoker(() =>
40.
{
41.
webBrowser1.Document.InvokeScript(
"ShowInfo"
);
42.
}));
43.
}
透過切割每個動作呼叫對應的 Invoke 去執行后執行結果如下
以上就是一個簡單使用 WinForm 的 WebBrowser 控件控制網頁元素的方式,在此紀錄一下做法。
范例程序代碼
http://www.it165.net/uploadfile/2013/1017/20131017072343466.rar