WebViewコントロールでの操作

WebViewコントロールでWebを操作する方法

WebBrowserコントロールでは直接DOM操作できるメソッド・プロパティ(getElementByIdメソッドやdocumentプロパティ等)がありましたが、 WebViewコントロールにはありません。

WebViewコントロールでは、InvokeScriptAsyncメソッドとJavaScriptのeval関数を使用して、JavaScriptのコードを使ってWebページを操作します。

また、WebページのJavaScriptからアプリケーションに情報を渡すことができます。
JavaScriptの「window.external.notify」と、アプリケーション側ではScriptNotifyイベントを使用します。

JavaScriptの関数を実行する/DOMを操作する

WebViewでJavaScriptを実行する場合、InvokeScript/InvokeScriptAsyncメソッドとJavaScriptのeval関数を使用します。

							Dim userIdText As String = "abcde"
							Dim functionString As String = String.Format("document.getElementById('userid').innerText = '{0}';", userIdText)
							WebView1.InvokeScript("eval", New String() {functionString})
						
							String userIdText = "abcde";
							String functionString = String.Format("document.getElementById('userid').innerText = '{0}';", userIdText);
							WebView1.InvokeScript("eval", New String[] { functionString });
						

InvokeScript/InvokeScriptAsyncメソッドの第一引数に"eval"、第二引数に実行したいJavaScriptのコードの文字列を渡します。
上記のようにエレメントに値を設定したりできます。

WebViewで表示しているページの情報を取得する

JavaScriptでwindow.external.notifyを使用することで、アプリケーションにイベントを通知して情報を渡すことができます。

まず、Webページ内の情報を受け取れるように、アプリケーション起動時にWebViewコントロールのIsScriptNotifyAllowedプロパティにTrueを設定します。

					'VB.NET
					WebView1.IsScriptNotifyAllowed = True

アプリケーション側でこれらのメッセージを受け取るためにScriptNotifyイベントを定義します。受け取った値はe.Valueで取得します。

					'VB.NET
					Private Sub OnWebViewScriptNotify(sender As Object, e As Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.WebViewControlScriptNotifyEventArgs) Handles WebView1.ScriptNotify
						MsgBox(e.Value)
					End Sub

WebViewのInvokeScriptメソッドと、JavaScriptのeval関数を使うのは上記「JavaScriptの関数を実行する」と一緒ですが、 実行するJavaScriptコードとしてwindow.external.notifyを使用します。
JavaScript内でwindow.external.notifyを実行すると、引数に指定した値がアプリケーション側にScriptNotifyイベントで渡されます。

					'VB.NET
					WebView1.InvokeScript("eval", New String() {"window.external.notify(String(document.getElementById('userid').value));"})

						Public Class Form1

							Private _getDataString As String    'WebViewからイベントで取得した文字列
							Private _getDataFlag As Boolean     'WebViewからイベントが発生したか
						
							Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
						
								WebView1.IsScriptNotifyAllowed = True    'これがないと、window.external.notifyでコード実行結果するとScriptNotifyイベントが発生しない。
						
							End Sub
						
							Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
						
								_getDataFlag = False
						
								'JavaScriptを実行
								WebView1.InvokeScript("eval", New String() {"window.external.notify(String(document.getElementById('userid').value));"})
						
								Application.DoEvents()
						
								'イベントが発生しているかチェック
								If _getDataFlag = False Then
									Return
								End If
						
								'取得できた値を表示
								MsgBox(_getDataString)
						
							End Sub
						
							Private Sub OnWebViewScriptNotify(sender As Object, e As Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.WebViewControlScriptNotifyEventArgs) Handles WebView1.ScriptNotify
						
								_getDataString = e.Value
								_getDataFlag = True
						
							End Sub

						End Class
					
						public partial class Form1 : Form
						{
					
							private string _getDataString;  //WebViewからイベントで取得した文字列
							private bool _getDataFlag;      //WebViewからイベントが発生したか
					
							public Form1()
							{
								InitializeComponent();
							}
					
							private void Form1_Load(object sender, EventArgs e)
							{
								webView1.IsScriptNotifyAllowed = true;   //これがないと、window.external.notifyでコード実行結果するとScriptNotifyイベントが発生しない。
							}
					
							//WebViewで表示しているページの情報を取得する
							private void Button1_Click(object sender, EventArgs e)
							{
								_getDataFlag = false;
					
								//JavaScriptを実行
								webView1.InvokeScript("eval", new string[] {"window.external.notify(String(document.getElementById(\'userid\').value));"});
					
								Application.DoEvents();
					
								//イベントが発生しているかチェック
								if (_getDataFlag == false) return;
					
								//取得できた値を表示
								MessageBox.Show(_getDataString);
							}
					
							private void OnWebViewScriptNotify(object sender, Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.WebViewControlScriptNotifyEventArgs e)
							{
								_getDataString = e.Value;
								_getDataFlag = true;
							}
						}					
					

JavaScriptで処理を行った結果を取得する

JavaScriptで処理を行った結果を「window.external.notify」で取得することができます。
JavaScriptのeval関数に渡すJavaScriptコードとして、 実行したい処理と、その処理結果を「window.external.notify」に渡すコードを組みます。

					'VB.NET
					WebView1.InvokeScript("eval", New String() {"var foo = document.querySelectorAll('.g');var str = """";foo.forEach(function (elem) {  str += elem.textContent + '\n';});window.external.notify(str);"})

window.external.notifyを使う場合の注意

ページの情報を取得するために、
					'VB.NET
					WebView1.InvokeScript("eval", New String() {"window.external.notify(String(document.getElementById('userid').value));"})
のように「window.external.notify」でイベントを発生させますが、使い方を誤るとエラーになったり正常に取得できません。
正常に動作しない場合は、以下のポイントを確認してください。

「window.external.notify」の引数はString型になっているか

「window.external.notify」の引数はString型でないとOnWebViewScriptNotifyイベントに値が渡されません。
Stringにキャストして値がわたるようにしましょう。

ダブルクォーテーションとシングルクォーテーションを使い分けているか

VB.NETでプログラムをする場合、2連続するダブルクォーテーションは実態は1つダブルクォーテーションになります。
意図せずダブルクォーテーションの数が減る場合がありますので、 JavaScriptのコード部分はシングルクォーテーションを使って区別しましょう。

						'以下はVisualStudioではエラーにならない。
						'しかし2連続のダブルクォーテーションは1つとして扱われるので、JavaScriptとしてはエラーになる。
						WebView1.InvokeScript("eval", New String() {"var str = "";window.external.notify(str);"})
				
						'シングルクォーテーションで記述しましょう。
						WebView1.InvokeScript("eval", New String() {"var str = '';window.external.notify(str);"})

WebViewのページ表示待ち方法

WebBrowserコントロールはIsBusy/ReadyStateプロパティで読み込み完了を判定していましたが、 WebViewにはこれらプロパティはありませんので、JavaScriptのdocument.readyStateを使います。
WebViewのNavigateメソッド実行後、InvokeScriptメソッドからWebView内のreadyStateの値を取得して読み込みが完了しているかループでチェックします。

以下のサンプルコードは、上記「JavaScriptの関数を実行する」のサンプルコードで使っている変数や設定を使ってますので、一部流用してください。

						Public Class Form1

							Private _getDataString As String    'WebViewからイベントで取得した文字列
							Private _getDataFlag As Boolean     'WebViewからイベントが発生したか
						
							Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
						
								WebView1.IsScriptNotifyAllowed = True    'これがないと、window.external.notifyでコード実行結果するとScriptNotifyイベントが発生しない。
						
							End Sub
						
							Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click

								Dim openFlag As Boolean = False '読み込み完了フラグ
						
								'ページを表示する
								WebView1.Navigate("https://web.biz-prog.net/")
						
								'読み込み待ち
								Dim timeout As Date = Now.AddMinutes(30)
								While Now < timeout
									System.Windows.Forms.Application.DoEvents()
									System.Threading.Thread.Sleep(100)
						
									'JavaScriptを実行
									_getDataFlag = False
									WebView1.InvokeScript("eval", New String() {"window.external.notify(String(document.readyState == 'complete'));"})
						
									Application.DoEvents()
						
									'イベントが発生しているかチェック
									If _getDataFlag = False Then
										MsgBox("err")
										Return
									End If
						
									'読み込み完了してれば終了       
									If _getDataString = "true" Then
										openFlag = True
										Exit While
									End If
								End While
						
								If openFlag = True Then
									MsgBox("ok")
								Else
									MsgBox("timeout")
								End If
						
								Return
						
							End Sub
											
							Private Sub OnWebViewScriptNotify(sender As Object, e As Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.WebViewControlScriptNotifyEventArgs) Handles WebView1.ScriptNotify
						
								_getDataString = e.Value
								_getDataFlag = True
						
							End Sub
										
						End Class
					
						public partial class Form1 : Form
						{
					
							private string _getDataString;  //WebViewからイベントで取得した文字列
							private bool _getDataFlag;      //WebViewからイベントが発生したか
					
							public Form1()
							{
								InitializeComponent();
							}
					
							private void Form1_Load(object sender, EventArgs e)
							{
								webView1.IsScriptNotifyAllowed = true;   //これがないと、window.external.notifyでコード実行結果するとScriptNotifyイベントが発生しない。
							}
					
							//WebViewのページ表示待ち方法
							private void button2_Click(object sender, EventArgs e)
							{
								bool openFlag = false;  //読み込み完了フラグ
					
								webView1.Navigate("https://web.biz-prog.net/");
					
								//読み込み待ち
								DateTime timeout = DateTime.Now.AddMinutes(30);
								while ((DateTime.Now < timeout))
								{
									System.Windows.Forms.Application.DoEvents();
									System.Threading.Thread.Sleep(100);
					
									//JavaScriptを実行
									_getDataFlag = false;
									webView1.InvokeScript("eval", new string[] {"window.external.notify(String(document.readyState == 'complete'));"});
					
									Application.DoEvents();
					
									//イベントが発生しているかチェック
									if (_getDataFlag == false)
									{
										MessageBox.Show("err");
										return;
									}
					
									//読み込み完了してれば終了       
									if ((_getDataString == "true"))
									{
										openFlag = true;
										break;
									}
					
								}
					
								if ((openFlag == true))
									MessageBox.Show("ok");
								else
									MessageBox.Show("timeout");
					
								return;
							}
				
					
							private void OnWebViewScriptNotify(object sender, Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.WebViewControlScriptNotifyEventArgs e)
							{
								_getDataString = e.Value;
								_getDataFlag = true;
							}
					
						}
					

WebViewのページ表示待ち方法2

ページの表示完了時に発生するイベントNavigationCompletedを使った表示待ちです。

						Public Class Form1

							Private ReadOnly condition As New System.Threading.CountdownEvent(1)

							Private Async Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
						
								Dim result As String = ""
						
								'非同期実行
								Await Task.Run(
									Sub()
										'ページの表示を開始
										WebView1.Navigate("https://web.biz-prog.net/")
						
										'読み込み完了まで待機
										If condition.Wait(5000) Then
											result = "ok"
										Else
											result = "timeout"
										End If
									End Sub
								)
						
								MsgBox(result)
						
							End Sub
						
							Private Sub WebView1_NavigationCompleted(sender As Object, e As Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.WebViewControlNavigationCompletedEventArgs) Handles WebView1.NavigationCompleted
						
								Console.WriteLine(e.Uri.ToString())
						
								'読み込み結果を判定
								If e.IsSuccess Then
									Console.WriteLine("complete")
								Else
									Console.WriteLine(e.WebErrorStatus)
								End If
						
								'シグナル初期化
								condition.Signal()
								System.Threading.Thread.Sleep(1)
								condition.Reset()
						
							End Sub
					
						End Class
					
						public partial class Form1 : Form
						{
					
							public Form1()
							{
								InitializeComponent();
							}
					
							private readonly System.Threading.CountdownEvent condition = new System.Threading.CountdownEvent(1);

							private async void button3_Click(object sender, EventArgs e)
							{
								string result = "";
					
								//非同期実行
								await Task.Run(() =>
								{
									//ページの表示を開始
									webView1.Navigate("https://web.biz-prog.net/");
					
									//読み込み完了まで待機
									if (condition.Wait(5000))
										result = "ok";
									else
										result = "timeout";
								});
					
								MessageBox.Show(result);
							}
					
							private void webView1_NavigationCompleted(object sender, Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.WebViewControlNavigationCompletedEventArgs e)
							{
								Console.WriteLine(e.Uri.ToString());
					
								//読み込み結果を判定
								if (e.IsSuccess)
									Console.WriteLine("complete");
								else
									Console.WriteLine(e.WebErrorStatus);
					
								//シグナル初期化
								condition.Signal();
								System.Threading.Thread.Sleep(1);
								condition.Reset();
							}
	
						}	
					

参考