WPF で Google Map その 2
WPF で Google Maps API を利用する方法その 2。前回は WebBrowser.InvokeScript メソッドを使用して C# のコードから JavaScript の関数を呼び出したが、今回は JavaScript から C# のコードにアクセスしてみる。
サンプル プログラム
前回のプログラムを修正 & 以下の機能を追加した。
- 現在位置の表示ボタンを押すと、地図中央の緯度・経度を表示
- 緯度・経度は JavaScript で取得してマネージコードのプロパティに設定する
作成したものは以下となる。
WebBrowser.ObjectForScripting 用データ クラス
WPF の WebBrowser には ObjectForScripting というプロパティが用意されている。このデータを設定すると、JavaScript 側は window.external のメンバとしてデータにアクセスできる。
ObjectForScripting の仕様については WebBrowser.ObjectForScripting プロパティを参照のこと。仕様どおり public かつ ComVisibleAttribute が true のクラスを作成する。INotifyPropertyChanged を実装、Location プロパティが更新されたら通知されるようにしておく。
/// <summary>
/// Google Map の操作を行う JavaScript に関連付けられるクラスです。
/// </summary>
[ComVisible( true )]
public class MapHost : INotifyPropertyChanged
{
/// <summary>
/// Google Map 上の移動座標。
/// </summary>
string _location;
/// <summary>
/// Google Map 上の緯度・経度を示す文字列を取得または設定します。
/// </summary>
public string Location
{
get
{
return this._location;
}
set
{
this._location = value;
this.NotifyPropertyChanged( "Location" );
}
}
#region INotifyPropertyChanged メンバ
/// <summary>
/// プロパティが変更された事を通知するイベント ハンドラ。
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// プロパティの変更を通知します。
/// </summary>
/// <param name="names">変更されたプロパティ名のコレクション。</param>
protected void NotifyPropertyChanged( params string[] names )
{
if( this.PropertyChanged != null )
{
foreach( string name in names )
{
this.PropertyChanged( this, new PropertyChangedEventArgs( name ) );
}
}
}
#endregion
}
クラスの先頭で ComVisibleAttribute に true を設定している。このようにするとクラスが COM として参照可能になる。ComVisibleAttribute が false、未指定、またはクラスのアクセス指定子へ public 以外を指定すると ObjectForScripting に割り当てた時点で例外が発生するため注意すること。
ObjectForScripting の Binding をサポートする
ObjectForScripting は依存プロパティではないため XAML 上で Binding できない。よって前回の WebBrowser.Source と同様に添付プロパティを利用する。
実装は以下のようになる。実際には WebBrowser.Source もこのクラスに含めているけど長くなるので割愛。
/// <summary>
/// WebBrowser クラス用の添付プロパティ クラスです。
/// </summary>
public static class WebBrowserBehavior
{
/// <summary>
/// ObjectForScriptingProperty を取得します。
/// </summary>
/// <param name="obj">依存プロパティ。</param>
/// <returns>取得した値。</returns>
public static string GetObjectForScripting( DependencyObject obj )
{
return ( string )obj.GetValue( ObjectForScriptingProperty );
}
/// <summary>
/// ObjectForScriptingProperty を設定します。
/// </summary>
/// <param name="obj">依存プロパティ。</param>
/// <param name="value">設定する値。</param>
public static void SetObjectForScripting( DependencyObject obj, string value )
{
obj.SetValue( ObjectForScriptingProperty, value );
}
/// <summary>
/// ObjectForScriptingProperty が変更された時に発生します。
/// </summary>
/// <param name="o">依存プロパティ。</param>
/// <param name="e">イベント データ。</param>
public static void OnObjectForScriptingPropertyChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
{
WebBrowser browser = o as WebBrowser;
if( browser != null )
{
browser.ObjectForScripting = e.NewValue;
}
}
/// <summary>
/// ObjectForScripting プロパティへの Binding 機能を提供する為の依存プロパティです。
/// </summary>
public static readonly DependencyProperty ObjectForScriptingProperty = DependencyProperty.RegisterAttached( "ObjectForScripting", typeof( object ), typeof( WebBrowserBehavior ), new UIPropertyMetadata( OnObjectForScriptingPropertyChanged ) );
}
メインウィンドウの XAML
前回の構造を踏襲しつつボタンとステータスバーを追加している。ObjectForScripting に MapHost を Binding しているので、この名前を持つプロパティが MainWindow.DataContext に存在すれば、WebBrowser 経由で JavaScript 側へ公開される。
<Window x:Class="WpfGoogleMapClient.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:WpfGoogleMapClient"
Title="Google Map API Client Test"
Height="480"
Width="640"
WindowStartupLocation="CenterScreen"
ResizeMode="CanResizeWithGrip">
<Grid Background="#dddddd" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- ツール バー -->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Margin="8,8,4,8" Text="{Binding Address}" />
<Button Grid.Column="1" Width="64" Margin="4,8,4,8" Click="OnClickGoButton">移動</Button>
<Button Grid.Column="2" Width="64" Margin="4,8,4,8" Click="OnClickGetLocation">現在位置</Button>
</Grid>
<!-- ブラウザ ( Google Map 表示 ) -->
<WebBrowser x:Name="_webBrowser" Grid.Row="1" Margin="8,0,8,8" l:WebBrowserBehavior.Source="{Binding Uri}" l:WebBrowserBehavior.ObjectForScripting="{Binding MapHost}" />
<!-- ステータス バー -->
<StatusBar Grid.Row="2">
<TextBlock Text="{Binding MapHost.Location}" />
</StatusBar>
</Grid>
</Window>
コードビ ハインドでは特別な事はしていないため、そちらの説明は省略。
JavaScript からのマネージコード呼び出し
WebBrowser.ObjectForScripting は JavaScript 上で window.external というオブジェクトとなり、以下のようにアクセスできる。
/**
* 現在位置の緯度・経度を取得してマネージ コードへ設定します。
*/
function getLocation()
{
window.external.Location = "緯度 : " + map.get_center().lat() + "、経度 : " + map.get_center().lng();
}
ObjectForScripting に割り当てた MapHost クラスは Location という string 型プロパティを公開しているため、window.external.Location と記述することでアクセス可能。MapHost.Location の変更は INotifyPropertyChanged により通知されてステータスバー表示へ反映される。サンプル プログラムを実行し、現在位置を表示すると以下のようになる。
今回のプログラムは単純ながら JavaScript との相互通信を実現できた。Google Maps API に詳しくなれば更に凝った処理も可能だろう。
次はピン操作あたりにチャレンジしたい。