アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

WPF で Google Map

WPF 上で Google Maps を利用する方法について。ざっとググッてみたが Google が公開している方法は面倒だし Codeplex のコントロールは多機能すぎて扱いにくそう。そのためより単純で簡単に利用する方法を検討してみる。

サンプル プログラム

以下のように Google Maps Client を設計。

  • 地図は WPF の WebBrowser コントロールで表示
  • WebBrowser に指定するのは Web サイトではなくローカルの HTML
  • WPF 部分に住所入力用のテキストボックスを持つ
  • WPF 部分の移動ボタンを押すと、地図がテキストボックスの住所に移動する

実際に作成したものは以下。

以降に実装手順と解説をまとめる。

Google Maps API 用 HTML

Google Maps API は基本的に Web アプリケーションとして利用するものだが HTML と JavaScript を実装すればローカルからも利用可能である。Google Maps API V2 までは利用するサイトの登録とキーの発行が必須であったが V3 よりこれを省略できるようになったので、今回はこれを利用する。

サンプル プログラムを表示するための HTML を定義。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- saved from url=(0017)http://localhost/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    <title>Map</title>
    <style type="text/css">
        html, body  { height: 100%; margin:0px; }
        #map_canvas { height: 100%; overflow:auto; }
    </style>
    <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
    <script type="text/javascript">
        //<![CDATA[

        var map;
        var geo;

        google.maps.event.addDomListener( window, 'load', function()
        {
            var mapdiv    = document.getElementById( "map_canvas" );
            var myOptions = { zoom: 16, center: new google.maps.LatLng( 35.686773, 139.68815 ), mapTypeId: google.maps.MapTypeId.ROADMAP, scaleControl: true };
            map           = new google.maps.Map( mapdiv, myOptions );
            geo           = new google.maps.Geocoder();
        });

        /**
         * 指定された住所の座標へ、マップを移動します。
         *
         * @param[in]   address 住所。
         */
        function moveMap( address )
        {
            if( geo )
            {
                geo.geocode( { 'address': address }, function( results, status )
                {
                    map.setCenter(results[ 0 ].geometry.location);
                } );
            }
        }

        //]]>
    </script>
</head>
<body>
    <div id="map_canvas"></div>
</body>
</html>

2 行目の saved from url という箇所は JavaScript を含むローカル HTML を開いた時に表示されるセキュリティ保護の警告を回避するためのおまじない。この設定については、Internet Explorer のマイ コンピュータ ゾーンのセキュリティ設定を強化する方法を参照の事。

10 行目は Google Maps の参照宣言。senser という引数は GPS などを持つ端末用。今回は使用しないので false としている。11 ~ 42 行目までは Map の初期化と Map 内の移動用スクリプトとなる。 個々の API について解説するのは大変なので重点だけ抜粋する。

初期化部分は 14 ~ 23 行目まで。グローバル宣言された map が地図、geo が座標情報の取得オブジェクト。これは mapMove 関数からも参照される。19 行目で Map を表示する要素を指定している。今回は map_canvas という id を持った divMap 領域とした。

WPF の WebBrowser 表示は IE 相当となる。そのため IE と同じく div の高さをそのまま 100% にできないので 6 ~ 9 行目のスタイルシートで強制的に 100% 表示となるように調整している。

moveMap 関数については、コメントと処理を見れば大まかな仕様が掴めると思う。

WebBrowser.Source の Binding

WPF の WebBrowser コントロールは Source というプロパティに URI を指定することで対象を表示する。しかし Source は依存プロパティではないため XAML 上から Binding による指定がおこなえず不便である。よってユーティリティ クラスを作成して使いやすくする。

このクラスは Stack Overflow の databind the Source property of the WebBrowser in WPF - Stack Overflow から引用したものである。

using System;
using System.Windows.Controls;
using System.Windows;

namespace WpfGoogleMapClient
{
    /// <summary>
    /// WebBrowser クラス用の Source プロパティへの Binding 機能を提供する為のユーティリティ クラスです。
    /// </summary>
    public static class WebBrowserUtility
    {
        /// <summary>
        /// BindableSourceProperty を取得します。
        /// </summary>
        /// <param name="obj">依存プロパティ。</param>
        /// <returns>取得した値。</returns>
        public static string GetBindableSource( DependencyObject obj )
        {
            return ( string )obj.GetValue( BindableSourceProperty );
        }

        /// <summary>
        /// BindableSourceProperty を設定します。
        /// </summary>
        /// <param name="obj">依存プロパティ。</param>
        /// <param name="value">設定する値。</param>
        public static void SetBindableSource( DependencyObject obj, string value )
        {
            obj.SetValue( BindableSourceProperty, value );
        }

        /// <summary>
        /// BindableSourceProperty が変更された時に発生します。
        /// </summary>
        /// <param name="o">依存プロパティ。</param>
        /// <param name="e">イベント データ。</param>
        public static void BindableSourcePropertyChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
        {
            WebBrowser browser = o as WebBrowser;
            if( browser != null )
            {
                string uri = e.NewValue as string;
                browser.Source = uri != null ? new Uri( uri ) : null;
            }
        }

        /// <summary>
        /// WebBrowser クラスの Source プロパティへの Binding 機能を提供する為の依存プロパティです。
        /// </summary>
        public static readonly DependencyProperty BindableSourceProperty = DependencyProperty.RegisterAttached( "BindableSource", typeof( string ), typeof( WebBrowserUtility ), new UIPropertyMetadata( null, BindableSourcePropertyChanged ) );
    }
}

メインウィンドウの XAML

WebBrowser.Source の Binding が可能となったので、メイン ウィンドウの XAML を定義する。

<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">
    <Grid Background="#dddddd" >
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <!-- ツール バー -->
        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <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>
        </Grid>

        <!-- ブラウザ ( Google Maps 表示 ) -->
        <WebBrowser x:Name="_webBrowser" Grid.Row="1" Margin="8,0,8,8" l:WebBrowserUtility.BindableSource="{Binding Uri}" />
    </Grid>
</Window>

テキストボックスの Text と、ブラウザの Source に Binding を指定している。これらは MainWindow クラスのデータコンテキストの Address、Uri プロパティに関連付けられる。

メインウィンドウのコードビハインド

メインウィンドウのコードビハインドは以下のようになる。

using System;
using System.Windows;

namespace WpfGoogleMapClient
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// インスタンスを初期化します。
        /// </summary>
        public MainWindow()
        {
            string uri = String.Format( "file://{0}index.html", AppDomain.CurrentDomain.BaseDirectory );
            this.DataContext = new MainWindowViewModel( uri ) { Address = "東京" };

            this.InitializeComponent();
        }

        /// <summary>
        /// 移動ボタンが押された時に発生するイベントです。
        /// </summary>
        /// <param name="sender">イベント発生元。</param>
        /// <param name="e">イベント データ。</param>
        private void OnClickGoButton( object sender, RoutedEventArgs e )
        {
            var context = this.DataContext as MainWindowViewModel;
            if( context == null ) { return; }

            // JavaScript の関数を呼び出す。
            this._webBrowser.InvokeScript( "moveMap", context.Address );
        }
    }

    /// <summary>
    /// MainWindow の Model と View を仲介するクラスです。
    /// </summary>
    class MainWindowViewModel
    {
        /// <summary>
        /// インスタンスを初期化します。
        /// </summary>
        /// <param name="uri">ブラウザに表示するページの URI。</param>
        public MainWindowViewModel( string uri )
        {
            this.Uri = uri;
        }
        /// <summary>
        /// Google Map に指定する住所を取得または設定します。
        /// </summary>
        public string Address { get; set; }

        /// <summary>
        /// ブラウザに表示するページの URI を取得します。
        /// </summary>
        public string Uri { get; private set; }
    }
}

特筆すべきは 16 行目の URI 指定と、32 行目の JavaScript 呼び出しである。

前者は、アプリケーションの実行ファイルのフォルダに置かれた index.html を URI としている。このファイルに、前述の Google Map 表示用の HTML が記述されている。

後者は WebBrowser.InvokeScript による、HTML 中の JavaScript 実行である。このメソッドは第一引数に関数名、第二引数に関数パラメータを指定。ウィンドウ上で移動ボタンが押された時、テキストボックスの内容 (に関連付いている MainWindowViewModel.Address ) を取得。それを JavaScript の moveMap へ指定することで実行している。

この処理により、JavaScript 側は指定住所から緯度と経度を算出し、その位置へ地図を移動させる。

プログラムの実行

このプログラムを実行すると Google Map が表示される。試しにテキストボックスに「原宿」と入力して移動ボタンを押すと、以下のように原宿駅に移動した。

サンプルプログラム

おなじみの Google Map が、デスクトップアプリ内で動いている様子は感慨深い。

2016/4/26
記事の冒頭にあげた Google 公式の WPF によるホスト方法の資料はリンク切れになっている。代替ページも見つからなかった。