S-JIS[2007-03-08/2007-03-30] 変更履歴

WebBrowser(VB.NET2003)

VisualBasic.NET2003のWebBrowser(InternetExplorerの機能を使ってHTMLを表示するコントロール)のメモです。

VB6(ExcelVBA)のWebBrowserコントロール
VC++2005のWebBrowserコントロール


使用準備

VB.NET2003では、デフォルトではウェブブラウザーコントロール(WebBrowser)を使えない。使えるようにする為には、プロジェクトに設定してやる必要がある。

  1. メニューバーの「ツール(T)」→「ツールボックスアイテムの追加と削除(X)」で、「ツールボックスのカスタマイズ」ダイアログを開く。
  2. 「COMコンポーネント」タブを選択する。
  3. 「Microsoft Web Browser」にチェックを入れる。(使用されるDLLはshdocvw.dll)

これにより ツールボックスの「全般」に「Microsoft Web Browser」が追加されるので、それをフォームに貼り付ける。
貼り付けると、 ソースには 以下のような変数が定義される。

    Friend WithEvents AxWebBrowser1 As AxSHDocVw.AxWebBrowser


また、表示しているHTMLを操作するのにmshtmlという名前空間にあるHTMLDocumentクラスElementクラスを使うことがあるが、それもデフォルトでは使えない。

  1. メニューバーの「プロジェクト(P)」→「参照の追加(R)」で「参照の追加」ダイアログを開く。
  2. 「COM」タブを選択する。
  3. 「Microsoft HTML Oject Library」の行を選択してから「選択(E)」ボタンを押す。

参考


ウェブサイトを表示

ウェブページを表示する例。

Private Sub 開始()

	AddHandler AxWebBrowser1.DocumentComplete, AddressOf DocumentCompleteHandler

	AxWebBrowser1.Navigate("http://www.ne.jp/asahi/hishidama/home/tech/index.html")

	'Navigateは、HTMLの読み込みを開始するだけ。
	'読み込みが終了していなくても次の処理が実行される。
End Sub
'HTMLの読込が完了したときに呼ばれるイベントハンドラーを自分で定義する
Private Sub DocumentCompleteHandler(ByVal sender As Object, ByVal e As AxSHDocVw.DWebBrowserEvents2_DocumentCompleteEvent)

	Debug.WriteLine("HTMLドキュメントの読み込み完了!")
End Sub

ブラウザーの変数DocumentCompleteイベント自作のイベントハンドラーを登録してやると、ドキュメント(HTML)の読込が完了したとき (かつ画面表示される前)に そのハンドラーが呼ばれる。
このハンドラーが呼び出されたら、その処理が終わるまで 画面表示されない。

ブラウザーの中でリンクをクリックして別ページに移動した場合も、そのページの読込が完了したときに再びこのハンドラーが呼ばれる。

※読み込みの完了を待つには、BusyやReadyStateを使う方法もある。ExcelのVBAのページを参照。たぶん同じだから。


上記のソースではイベントハンドラーの登録にAddHanlderを使っているが、 使わない方法もある。[2007-03-16]
イベントを発生させたいオブジェクト(の変数宣言)にWithEventsを付けて(AxWebBrowser1では自動的に付いている)、ハンドラーの関数(サブルーチン)にHandlesを追加すればよい。

'HTMLの読込が完了したときに呼ばれるイベントハンドラーを自分で定義する
Private Sub DocumentCompleteHandler(ByVal sender As Object, ByVal e As AxSHDocVw.DWebBrowserEvents2_DocumentCompleteEvent) _
	Handles AxWebBrowser1.DocumentComplete
〜
End Sub

Dim オブジェクト As クラス
AddHandler オブジェクト.イベント名, AddressOf ハンドラー関数
Function ハンドラー関数() As 型
 ↓↑
Dim WithEvents オブジェクト As クラス
Function ハンドラー関数() As 型 Handles オブジェクト.イベント名

ちなみに、Handlesの後ろにイベントをカンマ区切りで並べていくことにより、1つのハンドラーを複数のイベントで呼び出すようにできるらしい。


HTMLドキュメントの取得

HTML内部の操作(フォームの値の取得や設定、ボタンのクリック等)を行うには、まずWebBrowserからHTMLDocumentを取得する必要がある。
HTMLDocumentは、ドキュメントの読み込みが完了していないと使用できない。なので、HTML読込のハンドラーの中で取得しておくのがよいと思われる。

Dim doc As mshtml.HTMLDocument
'HTMLの読込が完了したときに呼ばれるイベントハンドラーを自分で定義する
Private Sub DocumentCompleteHandler(ByVal sender As Object, ByVal e As AxSHDocVw.DWebBrowserEvents2_DocumentCompleteEvent)

	'以前のオブジェクトを解放
	If Not doc Is Nothing Then
		System.Runtime.InteropServices.Marshal.ReleaseComObject(doc)
	End If

	doc = AxWebBrowser1.Document
End Sub

※読み込んだHTMLがフレームを使用していた場合は、このイベントは複数発生する


INPUTボックスの値の取得と設定

HTML内のフォームのINPUTタグの値を取得・設定できる。
アクセスしたいINPUTタグにidを付けておき、VBからはそのElementを取得し、それに対して操作する。

HTML:

	<input id="id1" name="name1"
	 type="text"
	 value="zzz">

VB:

Private Function 値取得() As String
	Dim ret As String

	Dim input1 As mshtml.HTMLInputElement
	input1 = doc.getElementById("id1")
	If Not input1 Is Nothing Then
		ret = input1.value	'値の取得
		input1.value = "abc"	'値の設定
		System.Runtime.InteropServices.Marshal.ReleaseComObject(input1)
	End If

	Return ret
End Function

名前(name)で取得することも出来る。[2007-03-16]
この場合、複数のタグで同じ名前が付いている可能性があるので、複数の要素が返る。

	Dim name1s As mshtml.IHTMLElementCollection
	name1s = d.getElementsByName("name1")
	For i As Integer = 0 To name1s.length - 1
		Dim n1 As mshtml.HTMLInputElement = name1s.item(i)
		Debug.WriteLine(n1.value)
	End If
	System.Runtime.InteropServices.Marshal.ReleaseComObject(name1s)
タグに該当するクラス[2007-03-21]
HTMLタグ クラス
input mshtml.HTMLInputElement
body mshtml.HTMLBodyClass
iframe mshtml.HTMLIFrameClass
div mshtml.HTMLDivElementClass

ボックスのスクロール

HTMLのdivやbody・iframe等のタグではスクロールすることができるが、VBからもスクロールさせることが出来る。[2007-03-21]
(要するにJavaScriptでスクロールさせるのと同じ)

VB:

	Dim scr1 As mshtml.HTMLDivElementClass
	scr1 = doc.getElementById("scroll_div")
	If Not scr1 Is Nothing Then
		'Debug.WriteLine(scr1.scrollLeft)
		'Debug.WriteLine(scr1.scrollTop)
		scr1.scrollTop = scr1.scrollHeight / 2	'指定位置へ移動
		System.Runtime.InteropServices.Marshal.ReleaseComObject(scr1)
	End If

ボタンのクリック

HTML上のボタンをクリックするのも簡単に出来る。

HTML:

	<input id="btn1"
	 type="button" value="ボタン1"
	 onclick="confirm('ボタン1が押された')">

VB:

Private Sub ボタンをクリックする()

	Dim button1 As mshtml.HTMLInputElement
	button1 = doc.getElementById("btn1")
	If Not button1 Is Nothing Then
		button1.click()	'ボタンをクリックする
		System.Runtime.InteropServices.Marshal.ReleaseComObject(button1)
	End If
End Sub

HTMLのボタン押下検知

HTMLのボタンがクリックされたことをVB側で検知することも出来る。
が、ちょっと技巧を要する。

基本的には 取得したエレメントにイベントハンドラーを登録してやることにより、HTMLでイベントが発生するとそのハンドラーが呼ばれる。
のだが、なぜかエレメントのデフォルト動作が行われなくなる
例えばボタンをクリックするとき、「マウスを押すとボタンが沈み、離すと元に戻る」のが通常の動作だが、ボタンが沈まなくなる!

これを回避する為に、隠れたINPUTタグを利用することにした。
本来対象としたいボタンonclick()で、隠れたINPUTタグをクリックしてやる。
そして、イベントハンドラー隠れたINPUTタグに対して登録しておく。
こうしておけば、ボタンの動作は通常通りで、(隠れたINPUTタグ経由で)VB側にイベントが通知される。

HTML:

	<input
	 type="button"
	 value="VBを呼び出すボタン"
	 onclick="hidden_button.click()">	<!--隠れたタグをクリックする-->

	<input id="hidden_button"		<!--こいつに対してクリックイベントを登録しておくと-->
	 type="hidden">			<!--クリック時VBのハンドラーが呼ばれる-->

VB:

Dim button2 As mshtml.HTMLInputElement
'HTMLの読込が完了したときに呼ばれるイベントハンドラーを自分で定義する
Private Sub DocumentCompleteHandler(ByVal sender As Object, ByVal e As AxSHDocVw.DWebBrowserEvents2_DocumentCompleteEvent)
	〜

	'以前のオブジェクトを解放
	If Not button2 Is Nothing Then
		System.Runtime.InteropServices.Marshal.ReleaseComObject(button2)
	End If

	button2 = doc.getElementById("hidden_button")
	If Not button2 Is Nothing Then
		AddHandler button2.onclick, AddressOf Button2ClickHandler
	End If
End Sub
'HTMLのボタンがクリックされたときに呼ばれるハンドラー
Private Function Button2ClickHandler() As Boolean

	MsgBox("HTMLのボタンがクリックされた", , "VBのダイアログ")

	Return True '何を返しても動作が変わらない気がする…
End Function

なお、ボタンを【マウスでクリック】した時だけでなく、【フォーカスが当たっているときのEnterキー押下】でも ちゃんとクリックイベントが発生する。


遷移前のイベント

WebBrowser内で別ページへ遷移する直前に、BeforeNavigate2というイベントが発生する。[2007-03-16]
(このイベント名でWebを検索すると「BeforeNavigate2のバグで イベントを捕捉できない」という情報を見かけることがあるが、自分が使ったVB.NET2003(.NET Framework 1.1.4322 SP1)では大丈夫だった)

Private Sub BeforeNavigate(ByVal sender As Object, ByVal e As AxSHDocVw.DWebBrowserEvents2_BeforeNavigate2Event) _
	Handles AxWebBrowser1.BeforeNavigate2
〜
End Sub
内容 備考 更新日
sender イベントが発生したWebBrowserのオブジェクト すなわちAxWebBrowser1と同じだが、このイベントに結び付けられているWebBrowserコントロールが複数あるときに区別できる。 2007-03-30
e.uRL 遷移先のURL    
e.headers      
e.targetFrameName フレーム名 HTMLのframeタグのname属性の値。frameタグでない場合は空文字列。 2007-03-30
e.postData メソッドがPOSTの場合に送信されるデータ バイト配列。ここに入っている値を書き換えても、実際に送信されるデータは変更されない。  
e.cancel キャンセルするかどうか ここにTrueを入れてハンドラー処理を終了すると、遷移がキャンセルされる。  

フレームの読み込み

HTMLのframeタグを使ってフレーム分割されているドキュメントをWebBrowserで読み込むときは、BeforeNavigateイベントDocumentCompleteイベントが複数回 発生する。[2007-03-30]

サンプル(frm.html):

<html>
<head><title>フレームのイベントのテスト</title></head>
<frameset rows="*,*">
<frame name="f1" src="test1.html">
<frame name="f2" src="test2.html">
</frameset>
</html>

このfrm.htmlを読み込んだとき、イベントは以下のように発生する。

  1. frm.htmlBeforeNavigate。e.uRLfrm.html、e.targetFrameNameは空文字列。
  2. フレーム1のBeforeNavigate。e.uRLtest1.html、e.targetFrameNamef1
  3. フレーム2のBeforeNavigate。e.uRLtest2.html、e.targetFrameNamef2
  4. フレーム1のDocumentComplete
  5. フレーム2のDocumentComplete
  6. frm.htmlDocumentComplete

注意:子フレームのイベントが発生する順序は、たぶん不定。親フレーム(frm.html)のイベントは、たぶん常に最初と最後に発生する。
いずれもイベントの引数のsenderはAxWebBrowser1なので、senderでは区別がつかない。
DocumentCompleteイベントで親フレームが完了した(つまり全ドキュメントを読み込んだ)事を確認するには、以下のようにする。

VB:

Private Sub DocumentCompleteHandler(ByVal sender As Object, ByVal e As AxSHDocVw.DWebBrowserEvents2_DocumentCompleteEvent) _
	Handles AxWebBrowser1.DocumentComplete

	If e.pDisp Is sender.Application Then
		'全て読み込み完了
	End If
〜
End Sub

(pDispとWebBrowser1.Objectを比較するよう書いてあるHPもあるが、それはたぶんVB6.0の方法。
VB.NET2003ではAxWebBrowser1.Objectは存在しない。AxWebBrowser1.Applicationでいいと思う)

※子フレームの中のHTMLだけが遷移した場合は、そのフレームのイベントしか発生しない。


DocumentCompleteイベントでフレーム名を取得するには、以下のようにする。

Private Sub DocumentCompleteHandler(ByVal sender As Object, ByVal e As AxSHDocVw.DWebBrowserEvents2_DocumentCompleteEvent) _
	Handles AxWebBrowser1.DocumentComplete

	Dim doc As mshtml.HTMLDocument = e.pDisp.Document
	Debug.WriteLine(doc.parentWindow.name) 'フレーム名
〜
End Sub

e.pDispはAxWebBrowserクラスではないようだが、e.pDisp.DocumentはHTMLDocumentのオブジェクトのようだ。


フレーム付きのドキュメントから特定のIDのHTMLエレメントを探すには、全フレームを再帰的に探索するしかないようだ。

'フレームの中から再帰的にIDのエレメントを探す(最初に見つかったエレメントを1つだけ返す)
Private Function getElementById(ByRef doc As mshtml.HTMLDocument, ByRef name As String) As mshtml.IHTMLElement
	Dim e As Object = doc.getElementById(name)
	If Not e Is Nothing Then
		Return e
	End If

	Dim c As mshtml.FramesCollection = doc.frames '全フレームを列挙
	For i As Integer = 0 To c.length - 1
		Dim f As mshtml.HTMLWindow2 = c.item(i)
		e = getElementById(f.document, name)
		If Not e Is Nothing Then
			Return e
		End If
	Next
End Function

参考


技術メモへ戻る
メールの送信先:ひしだま