アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

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 に詳しくなれば更に凝った処理も可能だろう。

次はピン操作あたりにチャレンジしたい。