WebViewコントロールで簡易Webブラウザを作るには?[Windows 8.1ストア・アプリ開発]:WinRT/Metro TIPS
Internet Explorerの機能を利用したWebViewコントロールを使って簡易的なWebブラウザのWindowsストア・アプリを作る方法を説明する。
powered by Insider.NET
Windowsストア・アプリのコントロールに、Internet Explorerの機能を利用したWebViewコントロール(Windows.UI.Xaml.Controls名前空間)がある。URLを与えるだけでWebページを表示できるので、これを使ってWindows 8(以降、Win 8)用に簡易的なWebブラウザを作ってみようとすると、それがなかなか難しい。例えば、Webページ閲覧履歴がないときに[戻る]/[進む]ボタンを実行不可状態にしようと思っても、うまく実現するすべがないのだ。どうにかならないだろうか?
ところが、Windows 8.1(以降、Win 8.1)用のWindowsストア・アプリ(以降、Win 8.1アプリ)のために用意された新しいWebViewコントロールは、大幅に改良されている。上に述べた課題も、あっさり解決できるのだ。そこで本稿では、Win 8.1アプリ用のWebViewコントロールを使って簡易Webブラウザを作る方法を解説する*1。本稿のサンプルは「Windows Store app samples:MetroTips #52(Windows 8.1版)」からダウンロードできる。
*1 本稿はPreview版に基づいて記述しているため、製品版では異なる可能性がある。あらかじめご承知おきいただきたい。
事前準備
Win 8.1アプリを開発するには、Win 8.1とVisual Studio 2013(以降、VS 2013)が必要である。本稿執筆時点ではまだ製品版は一般公開されておらず、本稿ではOracle VirtualBox上で64bit版Windows 8.1 Pro PreviewとVisual Studio Express 2013 Preview for Windowsを使用している。これらを準備する方法や注意事項は、「WinRT/Metro TIPS:Win8用のソース・コードをWin8.1用に変換するには?[Win 8.1]」の記事をご参照いただきたい。
問題点
Win 8用のWindowsストア・アプリ(以降、Win 8アプリ)に用意されていたWebViewコントロールには、いくつかの問題があった。例えば、次のようなものだ。
- 常に最前面に表示されてしまい、ほかのコントロールを手前に配置できなかった
- Webページの表示履歴に関する情報が全く取れず、[戻る]/[進む]ボタンの実行可能状態を制御するのは困難だった*2
- そのほか、例えばWebページのタイトルを知りたいというような、ちょっとしたことも簡単にできず、JavaScriptに頼る場面が多かった*3
*2 どのような困難があったのかは、MSDNのWindows Store appsサンプル「WebView の表示履歴管理を自前で行う」を参照してほしい。
*3 例えばWebページのタイトルを取得するために、JavaScriptのeval関数を利用して次のようなコードを書いていた(C#のコード)。
◎this.webView1.InvokeScript("eval", new[] { "document.title" });◎
改良されたWebViewコントロール
特集記事「大きく変わるWindowsストア・アプリ開発 〜 そのほかの変更点 (2/4)」でも紹介したが、新しいWebViewコントロールには次のような改良が加えられている。
- 常に最前面に表示されてしまうことはなくなった
- Opacityプロパティが有効になり、半透明にできるようになった
- GoBack/GoForward/RefreshやStopといったページ遷移用のメソッドが追加された
- CanGoBack/CanGoForwardやDocumentTitleといった状態を知るためのプロパティが充実した
最後に挙げたプロパティ名で見当が付くと思うが、[戻る]/[進む]ボタンの実行可能状態を簡単に制御できるようになったし、Webページのタイトルを取得するのもプロパティを参照するだけでよくなった。また、GoBackなどの新メソッドを使えば、[戻る]/[進む]ボタンの動作も簡単に実装できる。
画面の用意
それでは簡易Webブラウザを作っていこう。次のようなXAMLコードで画面を用意する。
……省略……
<!-- ここから -->
<Grid Grid.Row="1" Background="Gray">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- 表示しているページのタイトル -->
<TextBlock Margin="10,5" FontSize="18" TextTrimming="WordEllipsis" />
<!-- WebViewコントロール -->
<WebView x:Name="webView1" Grid.Row="1"
Source="http://www.atmarkit.co.jp/ait/subtop/features/dotnet/app/winrttips_index.html" />
<Grid Grid.Row="2" Background="#aa080044">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<!-- [戻る]ボタン -->
<AppBarButton Icon="Back" IsCompact="True" Margin="0,0,10,0" />
</StackPanel>
<!-- アドレス・バー -->
<TextBox x:Name="textBox1" Grid.Column="1" VerticalAlignment="Center" />
<StackPanel Grid.Column="2" Orientation="Horizontal">
<!-- [GO]ボタン -->
<Button Content="▶" Margin="-2,0" Padding="8,4" />
<!-- [リフレッシュ]ボタン -->
<AppBarButton Icon="Refresh" IsCompact="True" Margin="10,0,0,0" />
<!-- [進む]ボタン -->
<AppBarButton Icon="Forward" IsCompact="True" />
</StackPanel>
</Grid>
</Grid>
<!-- ここまで -->
……省略……
Win 8.1アプリ用に新設されたAppBarButtonコントロール(Windows.UI.Xaml.Controls名前空間)を使用している。
なお、VS 2013のPreview版では、XAMLエディタでAppBarButtonコントロールを編集する際に、次の画像のようなエラーが出ることがある。これは無視して、[OK]ボタンをクリックして作業を続けて構わない。
以上で実行してみると、次の画像のようになる。
用意した簡易Webブラウザの画面
これだけでWebページが表示される。リンクをたどってほかのページを見ることもできる。しかし、機能をまだ何も実装していないので、ボタンをタップしても何も起きないし、上部のテキストブロックや下部のテキストボックスも空欄のままだ。
WebViewコントロールのSourceプロパティにWebページのURL文字列を指定しておくだけで、このようにWebページが表示され、リンクをたどってほかのページを見ることもできる。ただし、別のウィンドウを開くように設定されているリンクでは、デフォルトのWebブラウザが起動してそちらに表示されてしまう*4。
*4 この挙動はWebViewコントロールの仕様であるとMSDNフォーラムで回答されている。回避するには、リンクを書き換えるJavaScriptを注入する(脚注*3参照)しかなさそうだ。
指定したURLを表示させるには?
下部のテキストボックス「アドレス・バー」にURLを入力し、その右にある右向き三角のボタン「[GO]ボタン」をタップしたときに、そのURLのWebページを表示しよう。この部分は、Win 8アプリと変わらない。Uriオブジェクト(System名前空間)を作成し、WebViewコントロールのNavigateメソッドを呼び出せばよい。
まず、[GO]ボタンにイベント・ハンドラの指定を追加する(次のコード)。
<!-- [GO]ボタン -->
<Button Content="▶" Margin="-2,0" Padding="8,4"
Tapped="buttonGo_Tapped" />
コードビハインドには、次のコードのようにイベント・ハンドラを実装する。
private async void buttonGo_Tapped(object sender, TappedRoutedEventArgs e)
{
Uri newUri;
if (Uri.TryCreate(this.textBox1.Text, UriKind.Absolute, out newUri)
&& newUri.Scheme.StartsWith("http"))
{
this.webView1.Navigate(newUri);
}
else
{
// エラー処理
string errMsg = @"入力された文字列がURIとして不正か、""http""で始まっていません";
await new Windows.UI.Popups.MessageDialog(errMsg).ShowAsync();
}
}
Private Async Sub buttonGo_Tapped(sender As Object, e As TappedRoutedEventArgs)
Dim newUri As Uri
If (Uri.TryCreate(Me.textBox1.Text, UriKind.Absolute, newUri) _
AndAlso newUri.Scheme.StartsWith("http")) Then
Me.webView1.Navigate(newUri)
Else
' エラー処理
Dim errMsg As String = "入力された文字列がURIとして不正か、""http""で始まっていません"
Await New Windows.UI.Popups.MessageDialog(errMsg).ShowAsync()
End If
End Sub
textBox1に入力されている文字列からUriオブジェクトの生成を試み、失敗したときはメッセージ・ダイアログを表示する。成功したら、WebViewコントロールのNavigateメソッドを呼び出してWebページを表示させている。
なお、エラー処理でメッセージ・ダイアログの非同期メソッドを呼び出しているため、メソッドのシグネチャにもasync/Asyncキーワードの追加が必要だ。
Just-In-Timeデバッガのエラー・ダイアログについて
ここまでのコードで任意のWebページを表示できるようになった。そこでいろいろとデバッグ実行をして試していると、次のようなエラーに遭遇することがあるだろう。デスクトップに突然切り替わって、次の画像のようなダイアログが出てくるのだ。ダイアログで[いいえ]を選んで無視し、再びアプリに切り替えて実行を継続して構わない。
これは、デバッグ実行中にアプリの外(WebViewコントロールではInternet Explorer(以降、IE)のエンジン内)で例外が発生したときに表示される。[利用可能なデバッガー]からVS 2013を選んでデバッグしてみても構わないが、IEのソース・コードはないのでマシン語でのデバッグ作業になるし、原因を突き止めたとしてもIEを修正することもできない。表示したWebページが原因でこの例外が出た場合は、アプリ側に悪い点はないし、実行を継続しても問題ない。そもそも、デバッグ実行ではなく、スタート画面から実行すれば、このエラーは出ない。
表示したWebページに原因があってIEが例外を出していると分かっている場合は、このJust-In-Timeデバッグを止めてしまって構わない。有償版のVS 2013では次のように簡単にできる。
VS 2013(有償版)のメニューバーから[デバッグ]−[オプションと設定]を選ぶと、上の図のダイアログが出てくる。その左側で[デバッグ]−[Just-In-Time]を選び、右側でチェックを外す。WebページのJavaScriptコードに起因するエラーの場合は、図のように[スクリプト]のチェックを外せばよい。FlashやSilverlightのエラーの場合は、[ネイティブ]や[マネージ]のチェックを外す必要も出てくるだろう。最後に[OK]ボタンをクリックして完了だ。
Express版では、このオプションが削られている。ブレーク・ポイントで止めたりする必要がないとき(=動作を確認するときなど)には、[デバッグ実行]ではなく[デバッグなしで開始](=メニューバーの[デバッグ]−[デバッグなしで開始])を使うとよい。また、レジストリを編集することでJust-In-Timeデバッグを止めることも可能のようだ*5。
*5 レジストリの編集はお勧めしないが、その方法はMSDNの「Just-In-Time Debugging in Visual Studio」に記載されている(本稿執筆時点では英語のみ)。
表示しているWebページのタイトルとURLを取得するには?
WebViewコントロールのDocumentTitleプロパティとSourceプロパティを使えばよい。ここでは画面に表示したいだけなので、次のコードのようにデータ・バインドするだけ済む。
<!-- 表示しているページのタイトル -->
<TextBlock Margin="10,5" FontSize="18" TextTrimming="WordEllipsis"
Text="{Binding DocumentTitle, ElementName=webView1, Mode=OneWay}" />
……省略……
<!-- アドレス・バー -->
<TextBox x:Name="textBox1" Grid.Column="1" VerticalAlignment="Center"
Text="{Binding Source, ElementName=webView1, Mode=OneWay}" />
表示しているページのタイトルにはWebViewコントロールのDocumentTitleプロパティを、アドレス・バーには同じくSourceプロパティをバインドした。
[戻る]/[進む]ボタンの実行可能状態を制御するには?
WebViewコントロールのCanGoBackプロパティとCanGoForwardプロパティを使えばよい。ボタンの実行可能状態と関連付けるには、次のコードのようにデータ・バインドするだけだ。
<!-- [戻る]ボタン -->
<AppBarButton Icon="Back" IsCompact="True" Margin="0,0,10,0"
IsEnabled="{Binding CanGoBack, ElementName=webView1, Mode=OneWay}" />
……省略……
<!-- [進む]ボタン -->
<AppBarButton Icon="Forward" IsCompact="True"
IsEnabled="{Binding CanGoForward, ElementName=webView1, Mode=OneWay}" />
[戻る]/[進む]/[リフレッシュ]ボタンの機能を実装するには?
それぞれのボタンのイベント・ハンドラで、WebViewコントロールの該当するメソッドを呼び出すだけでよい。
まず、ボタンにイベント・ハンドラの指定を追加する(次のコード)。
<!-- [戻る]ボタン -->
<AppBarButton Icon="Back" IsCompact="True" Margin="0,0,10,0"
Tapped="buttonGoBack_Tapped"
IsEnabled="{Binding CanGoBack, ElementName=webView1, Mode=OneWay}" />
……省略……
<!-- [リフレッシュ]ボタン -->
<AppBarButton Icon="Refresh" IsCompact="True" Margin="10,0,0,0"
Tapped="buttonRefresh_Tapped" />
<!-- [進む]ボタン -->
<AppBarButton Icon="Forward" IsCompact="True"
Tapped="buttonGoForward_Tapped"
IsEnabled="{Binding CanGoForward, ElementName=webView1, Mode=OneWay}" />
コードビハインドには、次のコードのようにイベント・ハンドラを実装する。
// [戻る]ボタンがタップされた
private void buttonGoBack_Tapped(object sender, TappedRoutedEventArgs e)
{
this.webView1.GoBack();
}
// [進む]ボタンがタップされた
private void buttonGoForward_Tapped(object sender, TappedRoutedEventArgs e)
{
this.webView1.GoForward();
}
// [リフレッシュ]ボタンがタップされた
private void buttonRefresh_Tapped(object sender, TappedRoutedEventArgs e)
{
this.webView1.Refresh();
}
' [戻る]ボタンがタップされた
Private Sub buttonGoBack_Tapped(sender As Object, e As TappedRoutedEventArgs)
Me.webView1.GoBack()
End Sub
' [進む]ボタンがタップされた
Private Sub buttonGoForward_Tapped(sender As Object, e As TappedRoutedEventArgs)
Me.webView1.GoForward()
End Sub
' [リフレッシュ]ボタンがタップされた
Private Sub buttonRefresh_Tapped(sender As Object, e As TappedRoutedEventArgs)
Me.webView1.Refresh()
End Sub
それぞれ、WebViewコントロールの対応するメソッドを呼び出すだけだ。
なお、上のコードをPreview版で試した場合には、スクロール位置が復元されない。これは、Win 8.1製品版とVS 2013 RCの組み合わせでは直っている。
Webサーバからのエラーに対処するには?
WebViewコントロールのNavigationCompletedイベントで、Webサーバからのエラーも一緒に処理すればよい。
まず、コードビハインドのコンストラクタで、イベント・ハンドラを結びつける(次のコード)。
public MainPage()
{
……省略……
this.webView1.NavigationCompleted += webView1_NavigationCompleted;
}
Public Sub New()
……省略……
AddHandler Me.webView1.NavigationCompleted, AddressOf webView1_NavigationCompleted
End Sub
次に、イベント・ハンドラでは次のコードのように、第2引数のWebViewNavigationCompletedEventArgsオブジェクト(Windows.UI.Xaml.Controls名前空間)のIsSuccessプロパティを見てWebサーバからのエラーの有無を判断し、WebErrorStatusプロパティでエラーの内容を取得する。
async void webView1_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
{
if (!args.IsSuccess)
{
// エラー発生
string errMsg = args.WebErrorStatus.ToString();
int errCode = (int)args.WebErrorStatus;
string msg = string.Format("サーバ側エラー:{0}({1})", errMsg, errCode);
await new Windows.UI.Popups.MessageDialog(msg).ShowAsync();
}
}
Private Async Sub webView1_NavigationCompleted(sender As WebView, args As WebViewNavigationCompletedEventArgs)
If (Not args.IsSuccess) Then
' エラー発生
Dim errMsg As String = args.WebErrorStatus.ToString()
Dim errCode As Integer = CInt(args.WebErrorStatus)
Dim msg As String = String.Format("サーバ側エラー:{0} ({1})", errMsg, errCode)
Await New Windows.UI.Popups.MessageDialog(msg).ShowAsync()
End If
End Sub
errCodeは、300以降はサーバからのHTTPステータス・コードになっている。
なお、メッセージ・ダイアログの非同期メソッドを呼び出しているため、メソッドのシグネチャにもasync/Asyncキーワードが必要だ。
これで、例えばWebサーバからHTTPステータス・コード404が返されたときは、「サーバ側エラー:NotFound(404)」というメッセージが表示される。WebErrorStatus列挙体の値は、300以降はHTTPステータス・コードと同じになっているのだ。
実行結果
以上で完成だ。実行してみると次の画面のようになる。
表示しているWebページが変わると、上部のページのタイトルと、下部のアドレス・バーのURLが変わる。[戻る]/[進む]ボタンも想定どおりに機能するはずだ。
Webサーバに存在しないWebページを表示させようとすると、次の画面のようになる。
Webサーバが返してきた「指定されたページは存在しません」というページとともに、「サーバ側エラー:NotFound(404)」というメッセージ・ダイアログが表示されている。
まとめ
Win 8.1アプリ用のWebViewコントロールは、格段に使いやすくなった。簡易的なWebブラウザ程度なら、とても簡単に実装できる。
Copyright© Digital Advantage Corp. All Rights Reserved.